aboutsummaryrefslogtreecommitdiff
path: root/src/SFileVerify.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/SFileVerify.cpp')
-rw-r--r--src/SFileVerify.cpp2108
1 files changed, 1054 insertions, 1054 deletions
diff --git a/src/SFileVerify.cpp b/src/SFileVerify.cpp
index 45ff58e..e68d51a 100644
--- a/src/SFileVerify.cpp
+++ b/src/SFileVerify.cpp
@@ -1,1054 +1,1054 @@
-/*****************************************************************************/
-/* SFileVerify.cpp Copyright (c) Ladislav Zezula 2010 */
-/*---------------------------------------------------------------------------*/
-/* MPQ files and MPQ archives verification. */
-/* */
-/* The MPQ signature verification has been written by Jean-Francois Roy */
-/* <bahamut@macstorm.org> and Justin Olbrantz (Quantam). */
-/* The MPQ public keys have been created by MPQKit, using OpenSSL library. */
-/* */
-/*---------------------------------------------------------------------------*/
-/* Date Ver Who Comment */
-/* -------- ---- --- ------- */
-/* 04.05.10 1.00 Lad The first version of SFileVerify.cpp */
-/*****************************************************************************/
-
-#define __STORMLIB_SELF__
-#include "StormLib.h"
-#include "StormCommon.h"
-
-//-----------------------------------------------------------------------------
-// Local defines
-
-#define MPQ_DIGEST_UNIT_SIZE 0x10000
-
-//-----------------------------------------------------------------------------
-// Known Blizzard public keys
-// Created by Jean-Francois Roy using OpenSSL
-
-static const char * szBlizzardWeakPrivateKey =
- "-----BEGIN PRIVATE KEY-----"
- "MIIBOQIBAAJBAJJidwS/uILMBSO5DLGsBFknIXWWjQJe2kfdfEk3G/j66w4KkhZ1"
- "V61Rt4zLaMVCYpDun7FLwRjkMDSepO1q2DcCAwEAAQJANtiztVDMJh2hE1hjPDKy"
- "UmEJ9U/aN3gomuKOjbQbQ/bWWcM/WfhSVHmPqtqh/bQI2UXFr0rnXngeteZHLr/b"
- "8QIhAMuWriSKGMACw18/rVVfUrThs915odKBH1Alr3vMVVzZAiEAuBHPSQkgwcb6"
- "L4MWaiKuOzq08mSyNqPeN8oSy18q848CIHeMn+3s+eOmu7su1UYQl6yH7OrdBd1q"
- "3UxfFNEJiAbhAiAqxdCyOxHGlbM7aS3DOg3cq5ayoN2cvtV7h1R4t8OmVwIgF+5z"
- "/6vkzBUsZhd8Nwyis+MeQYH0rpFpMKdTlqmPF2Q="
- "-----END PRIVATE KEY-----";
-
-static const char * szBlizzardWeakPublicKey =
- "-----BEGIN PUBLIC KEY-----"
- "MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAJJidwS/uILMBSO5DLGsBFknIXWWjQJe"
- "2kfdfEk3G/j66w4KkhZ1V61Rt4zLaMVCYpDun7FLwRjkMDSepO1q2DcCAwEAAQ=="
- "-----END PUBLIC KEY-----";
-
-static const char * szBlizzardStrongPublicKey =
- "-----BEGIN PUBLIC KEY-----"
- "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAsQZ+ziT2h8h+J/iMQpgd"
- "tH1HaJzOBE3agjU4yMPcrixaPOZoA4t8bwfey7qczfWywocYo3pleytFF+IuD4HD"
- "Fl9OXN1SFyupSgMx1EGZlgbFAomnbq9MQJyMqQtMhRAjFgg4TndS7YNb+JMSAEKp"
- "kXNqY28n/EVBHD5TsMuVCL579gIenbr61dI92DDEdy790IzIG0VKWLh/KOTcTJfm"
- "Ds/7HQTkGouVW+WUsfekuqNQo7ND9DBnhLjLjptxeFE2AZqYcA1ao3S9LN3GL1tW"
- "lVXFIX9c7fWqaVTQlZ2oNsI/ARVApOK3grNgqvwH6YoVYVXjNJEo5sQJsPsdV/hk"
- "dwIDAQAB"
- "-----END PUBLIC KEY-----";
-
-static const char * szWarcraft3MapPublicKey =
- "-----BEGIN PUBLIC KEY-----"
- "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA1BwklUUQ3UvjizOBRoF5"
- "yyOVc7KD+oGOQH5i6eUk1yfs0luCC70kNucNrfqhmviywVtahRse1JtXCPrx2bd3"
- "iN8Dx91fbkxjYIOGTsjYoHKTp0BbaFkJih776fcHgnFSb+7mJcDuJVvJOXxEH6w0"
- "1vo6VtujCqj1arqbyoal+xtAaczF3us5cOEp45sR1zAWTn1+7omN7VWV4QqJPaDS"
- "gBSESc0l1grO0i1VUSumayk7yBKIkb+LBvcG6WnYZHCi7VdLmaxER5m8oZfER66b"
- "heHoiSQIZf9PAY6Guw2DT5BTc54j/AaLQAKf2qcRSgQLVo5kQaddF3rCpsXoB/74"
- "6QIDAQAB"
- "-----END PUBLIC KEY-----";
-
-static const char * szWowPatchPublicKey =
- "-----BEGIN PUBLIC KEY-----"
- "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwOsMV0LagAWPEtEQM6b9"
- "6FHFkUyGbbyda2/Dfc9dyl21E9QvX+Yw7qKRMAKPzA2TlQQLZKvXpnKXF/YIK5xa"
- "5uwg9CEHCEAYolLG4xn0FUOE0E/0PuuytI0p0ICe6rk00PifZzTr8na2wI/l/GnQ"
- "bvnIVF1ck6cslATpQJ5JJVMXzoFlUABS19WESw4MXuJAS3AbMhxNWdEhVv7eO51c"
- "yGjRLy9QjogZODZTY0fSEksgBqQxNCoYVJYI/sF5K2flDsGqrIp0OdJ6teJlzg1Y"
- "UjYnb6bKjlidXoHEXI2TgA/mD6O3XFIt08I9s3crOCTgICq7cgX35qrZiIVWZdRv"
- "TwIDAQAB"
- "-----END PUBLIC KEY-----";
-
-static const char * szWowSurveyPublicKey =
- "-----BEGIN PUBLIC KEY-----"
- "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAnIt1DR6nRyyKsy2qahHe"
- "MKLtacatn/KxieHcwH87wLBxKy+jZ0gycTmJ7SaTdBAEMDs/V5IPIXEtoqYnid2c"
- "63TmfGDU92oc3Ph1PWUZ2PWxBhT06HYxRdbrgHw9/I29pNPi/607x+lzPORITOgU"
- "BR6MR8au8HsQP4bn4vkJNgnSgojh48/XQOB/cAln7As1neP61NmVimoLR4Bwi3zt"
- "zfgrZaUpyeNCUrOYJmH09YIjbBySTtXOUidoPHjFrMsCWpr6xs8xbETbs7MJFL6a"
- "vcUfTT67qfIZ9RsuKfnXJTIrV0kwDSjjuNXiPTmWAehSsiHIsrUXX5RNcwsSjClr"
- "nQIDAQAB"
- "-----END PUBLIC KEY-----";
-
-static const char * szStarcraft2MapPublicKey =
- "-----BEGIN PUBLIC KEY-----"
- "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAmk4GT8zb+ICC25a17KZB"
- "q/ygKGJ2VSO6IT5PGHJlm1KfnHBA4B6SH3xMlJ4c6eG2k7QevZv+FOhjsAHubyWq"
- "2VKqWbrIFKv2ILc2RfMn8J9EDVRxvcxh6slRrVL69D0w1tfVGjMiKq2Fym5yGoRT"
- "E7CRgDqbAbXP9LBsCNWHiJLwfxMGzHbk8pIl9oia5pvM7ofZamSHchxlpy6xa4GJ"
- "7xKN01YCNvklTL1D7uol3wkwcHc7vrF8QwuJizuA5bSg4poEGtH62BZOYi+UL/z0"
- "31YK+k9CbQyM0X0pJoJoYz1TK+Y5J7vBnXCZtfcTYQ/ZzN6UcxTa57dJaiOlCh9z"
- "nQIDAQAB"
- "-----END PUBLIC KEY-----";
-
-//-----------------------------------------------------------------------------
-// Local functions
-
-static void memrev(unsigned char *buf, size_t count)
-{
- unsigned char *r;
-
- for (r = buf + count - 1; buf < r; buf++, r--)
- {
- *buf ^= *r;
- *r ^= *buf;
- *buf ^= *r;
- }
-}
-
-static bool decode_base64_key(const char * szKeyBase64, rsa_key * key)
-{
- unsigned char decoded_key[0x200];
- const char * szBase64Begin;
- const char * szBase64End;
- unsigned long decoded_length = sizeof(decoded_key);
- unsigned long length;
-
- // Find out the begin of the BASE64 data
- szBase64Begin = szKeyBase64 + strlen("-----BEGIN PUBLIC KEY-----");
- szBase64End = szBase64Begin + strlen(szBase64Begin) - strlen("-----END PUBLIC KEY-----");
- if(szBase64End[0] != '-')
- return false;
-
- // decode the base64 string
- length = (unsigned long)(szBase64End - szBase64Begin);
- if(base64_decode((unsigned char *)szBase64Begin, length, decoded_key, &decoded_length) != CRYPT_OK)
- return false;
-
- // Create RSA key
- if(rsa_import(decoded_key, decoded_length, key) != CRYPT_OK)
- return false;
-
- return true;
-}
-
-static void GetPlainAnsiFileName(
- const TCHAR * szFileName,
- char * szPlainName)
-{
- const TCHAR * szPlainNameT = GetPlainFileName(szFileName);
-
- // Convert the plain name to ANSI
- while(*szPlainNameT != 0)
- *szPlainName++ = (char)*szPlainNameT++;
- *szPlainName = 0;
-}
-
-// Calculate begin and end of the MPQ archive
-static void CalculateArchiveRange(
- TMPQArchive * ha,
- PMPQ_SIGNATURE_INFO pSI)
-{
- ULONGLONG TempPos = 0;
- char szMapHeader[0x200];
-
- // Get the MPQ begin
- pSI->BeginMpqData = ha->MpqPos;
-
- // Warcraft III maps are signed from the map header to the end
- if(FileStream_Read(ha->pStream, &TempPos, szMapHeader, sizeof(szMapHeader)))
- {
- // Is it a map header ?
- if(szMapHeader[0] == 'H' && szMapHeader[1] == 'M' && szMapHeader[2] == '3' && szMapHeader[3] == 'W')
- {
- // We will have to hash since the map header
- pSI->BeginMpqData = 0;
- }
- }
-
- // Get the MPQ data end. This is stored in the MPQ header
- pSI->EndMpqData = ha->MpqPos + ha->pHeader->ArchiveSize64;
-
- // Get the size of the entire file
- FileStream_GetSize(ha->pStream, &pSI->EndOfFile);
-}
-
-static bool CalculateMpqHashMd5(
- TMPQArchive * ha,
- PMPQ_SIGNATURE_INFO pSI,
- LPBYTE pMd5Digest)
-{
- hash_state md5_state;
- ULONGLONG BeginBuffer;
- ULONGLONG EndBuffer;
- LPBYTE pbDigestBuffer = NULL;
-
- // Allocate buffer for creating the MPQ digest.
- pbDigestBuffer = STORM_ALLOC(BYTE, MPQ_DIGEST_UNIT_SIZE);
- if(pbDigestBuffer == NULL)
- return false;
-
- // Initialize the MD5 hash state
- md5_init(&md5_state);
-
- // Set the byte offset of begin of the data
- BeginBuffer = pSI->BeginMpqData;
-
- // Create the digest
- for(;;)
- {
- ULONGLONG BytesRemaining;
- LPBYTE pbSigBegin = NULL;
- LPBYTE pbSigEnd = NULL;
- DWORD dwToRead = MPQ_DIGEST_UNIT_SIZE;
-
- // Check the number of bytes remaining
- BytesRemaining = pSI->EndMpqData - BeginBuffer;
- if(BytesRemaining < MPQ_DIGEST_UNIT_SIZE)
- dwToRead = (DWORD)BytesRemaining;
- if(dwToRead == 0)
- break;
-
- // Read the next chunk
- if(!FileStream_Read(ha->pStream, &BeginBuffer, pbDigestBuffer, dwToRead))
- {
- STORM_FREE(pbDigestBuffer);
- return false;
- }
-
- // Move the current byte offset
- EndBuffer = BeginBuffer + dwToRead;
-
- // Check if the signature is within the loaded digest
- if(BeginBuffer <= pSI->BeginExclude && pSI->BeginExclude < EndBuffer)
- pbSigBegin = pbDigestBuffer + (size_t)(pSI->BeginExclude - BeginBuffer);
- if(BeginBuffer <= pSI->EndExclude && pSI->EndExclude < EndBuffer)
- pbSigEnd = pbDigestBuffer + (size_t)(pSI->EndExclude - BeginBuffer);
-
- // Zero the part that belongs to the signature
- if(pbSigBegin != NULL || pbSigEnd != NULL)
- {
- if(pbSigBegin == NULL)
- pbSigBegin = pbDigestBuffer;
- if(pbSigEnd == NULL)
- pbSigEnd = pbDigestBuffer + dwToRead;
-
- memset(pbSigBegin, 0, (pbSigEnd - pbSigBegin));
- }
-
- // Pass the buffer to the hashing function
- md5_process(&md5_state, pbDigestBuffer, dwToRead);
-
- // Move pointers
- BeginBuffer += dwToRead;
- }
-
- // Finalize the MD5 hash
- md5_done(&md5_state, pMd5Digest);
- STORM_FREE(pbDigestBuffer);
- return true;
-}
-
-static void AddTailToSha1(
- hash_state * psha1_state,
- const char * szTail)
-{
- unsigned char * pbTail = (unsigned char *)szTail;
- unsigned char szUpperCase[0x200];
- unsigned long nLength = 0;
-
- // Convert the tail to uppercase
- // Note that we don't need to terminate the string with zero
- while(*pbTail != 0)
- {
- szUpperCase[nLength++] = AsciiToUpperTable[*pbTail++];
- }
-
- // Append the tail to the SHA1
- sha1_process(psha1_state, szUpperCase, nLength);
-}
-
-static bool CalculateMpqHashSha1(
- TMPQArchive * ha,
- PMPQ_SIGNATURE_INFO pSI,
- unsigned char * sha1_tail0,
- unsigned char * sha1_tail1,
- unsigned char * sha1_tail2)
-{
- ULONGLONG BeginBuffer;
- hash_state sha1_state_temp;
- hash_state sha1_state;
- LPBYTE pbDigestBuffer = NULL;
- char szPlainName[MAX_PATH];
-
- // Allocate buffer for creating the MPQ digest.
- pbDigestBuffer = STORM_ALLOC(BYTE, MPQ_DIGEST_UNIT_SIZE);
- if(pbDigestBuffer == NULL)
- return false;
-
- // Initialize SHA1 state structure
- sha1_init(&sha1_state);
-
- // Calculate begin of data to be hashed
- BeginBuffer = pSI->BeginMpqData;
-
- // Create the digest
- for(;;)
- {
- ULONGLONG BytesRemaining;
- DWORD dwToRead = MPQ_DIGEST_UNIT_SIZE;
-
- // Check the number of bytes remaining
- BytesRemaining = pSI->EndMpqData - BeginBuffer;
- if(BytesRemaining < MPQ_DIGEST_UNIT_SIZE)
- dwToRead = (DWORD)BytesRemaining;
- if(dwToRead == 0)
- break;
-
- // Read the next chunk
- if(!FileStream_Read(ha->pStream, &BeginBuffer, pbDigestBuffer, dwToRead))
- {
- STORM_FREE(pbDigestBuffer);
- return false;
- }
-
- // Pass the buffer to the hashing function
- sha1_process(&sha1_state, pbDigestBuffer, dwToRead);
-
- // Move pointers
- BeginBuffer += dwToRead;
- }
-
- // Add all three known tails and generate three hashes
- memcpy(&sha1_state_temp, &sha1_state, sizeof(hash_state));
- sha1_done(&sha1_state_temp, sha1_tail0);
-
- memcpy(&sha1_state_temp, &sha1_state, sizeof(hash_state));
- GetPlainAnsiFileName(FileStream_GetFileName(ha->pStream), szPlainName);
- AddTailToSha1(&sha1_state_temp, szPlainName);
- sha1_done(&sha1_state_temp, sha1_tail1);
-
- memcpy(&sha1_state_temp, &sha1_state, sizeof(hash_state));
- AddTailToSha1(&sha1_state_temp, "ARCHIVE");
- sha1_done(&sha1_state_temp, sha1_tail2);
-
- // Finalize the MD5 hash
- STORM_FREE(pbDigestBuffer);
- return true;
-}
-
-static int VerifyRawMpqData(
- TMPQArchive * ha,
- ULONGLONG ByteOffset,
- DWORD dwDataSize)
-{
- ULONGLONG DataOffset = ha->MpqPos + ByteOffset;
- LPBYTE pbDataChunk;
- LPBYTE pbMD5Array1; // Calculated MD5 array
- LPBYTE pbMD5Array2; // MD5 array loaded from the MPQ
- DWORD dwBytesInChunk;
- DWORD dwChunkCount;
- DWORD dwChunkSize = ha->pHeader->dwRawChunkSize;
- DWORD dwMD5Size;
- int nError = ERROR_SUCCESS;
-
- // Don't verify zero-sized blocks
- if(dwDataSize == 0)
- return ERROR_SUCCESS;
-
- // Get the number of data chunks to calculate MD5
- assert(dwChunkSize != 0);
- dwChunkCount = ((dwDataSize - 1) / dwChunkSize) + 1;
- dwMD5Size = dwChunkCount * MD5_DIGEST_SIZE;
-
- // Allocate space for data chunk and for the MD5 array
- pbDataChunk = STORM_ALLOC(BYTE, dwChunkSize);
- if(pbDataChunk == NULL)
- return ERROR_NOT_ENOUGH_MEMORY;
-
- // Allocate space for MD5 array
- pbMD5Array1 = STORM_ALLOC(BYTE, dwMD5Size);
- pbMD5Array2 = STORM_ALLOC(BYTE, dwMD5Size);
- if(pbMD5Array1 == NULL || pbMD5Array2 == NULL)
- nError = ERROR_NOT_ENOUGH_MEMORY;
-
- // Calculate MD5 of each data chunk
- if(nError == ERROR_SUCCESS)
- {
- LPBYTE pbMD5 = pbMD5Array1;
-
- for(DWORD i = 0; i < dwChunkCount; i++)
- {
- // Get the number of bytes in the chunk
- dwBytesInChunk = STORMLIB_MIN(dwChunkSize, dwDataSize);
-
- // Read the data chunk
- if(!FileStream_Read(ha->pStream, &DataOffset, pbDataChunk, dwBytesInChunk))
- {
- nError = ERROR_FILE_CORRUPT;
- break;
- }
-
- // Calculate MD5
- CalculateDataBlockHash(pbDataChunk, dwBytesInChunk, pbMD5);
-
- // Move pointers and offsets
- DataOffset += dwBytesInChunk;
- dwDataSize -= dwBytesInChunk;
- pbMD5 += MD5_DIGEST_SIZE;
- }
- }
-
- // Read the MD5 array
- if(nError == ERROR_SUCCESS)
- {
- // Read the array of MD5
- if(!FileStream_Read(ha->pStream, &DataOffset, pbMD5Array2, dwMD5Size))
- nError = GetLastError();
- }
-
- // Compare the array of MD5
- if(nError == ERROR_SUCCESS)
- {
- // Compare the MD5
- if(memcmp(pbMD5Array1, pbMD5Array2, dwMD5Size))
- nError = ERROR_FILE_CORRUPT;
- }
-
- // Free memory and return result
- if(pbMD5Array2 != NULL)
- STORM_FREE(pbMD5Array2);
- if(pbMD5Array1 != NULL)
- STORM_FREE(pbMD5Array1);
- if(pbDataChunk != NULL)
- STORM_FREE(pbDataChunk);
- return nError;
-}
-
-static DWORD VerifyWeakSignature(
- TMPQArchive * ha,
- PMPQ_SIGNATURE_INFO pSI)
-{
- BYTE RevSignature[MPQ_WEAK_SIGNATURE_SIZE];
- BYTE Md5Digest[MD5_DIGEST_SIZE];
- rsa_key key;
- int hash_idx = find_hash("md5");
- int result = 0;
-
- // The signature might be zeroed out. In that case, we ignore it
- if(!IsValidSignature(pSI->Signature))
- return ERROR_WEAK_SIGNATURE_OK;
-
- // Calculate hash of the entire archive, skipping the (signature) file
- if(!CalculateMpqHashMd5(ha, pSI, Md5Digest))
- return ERROR_VERIFY_FAILED;
-
- // Import the Blizzard key in OpenSSL format
- if(!decode_base64_key(szBlizzardWeakPublicKey, &key))
- return ERROR_VERIFY_FAILED;
-
- // Verify the signature
- memcpy(RevSignature, &pSI->Signature[8], MPQ_WEAK_SIGNATURE_SIZE);
- memrev(RevSignature, MPQ_WEAK_SIGNATURE_SIZE);
- rsa_verify_hash_ex(RevSignature, MPQ_WEAK_SIGNATURE_SIZE, Md5Digest, sizeof(Md5Digest), LTC_LTC_PKCS_1_V1_5, hash_idx, 0, &result, &key);
- rsa_free(&key);
-
- // Return the result
- return result ? ERROR_WEAK_SIGNATURE_OK : ERROR_WEAK_SIGNATURE_ERROR;
-}
-
-static DWORD VerifyStrongSignatureWithKey(
- unsigned char * reversed_signature,
- unsigned char * padded_digest,
- const char * szPublicKey)
-{
- rsa_key key;
- int result = 0;
-
- // Import the Blizzard key in OpenSSL format
- if(!decode_base64_key(szPublicKey, &key))
- {
- assert(false);
- return ERROR_VERIFY_FAILED;
- }
-
- // Verify the signature
- if(rsa_verify_simple(reversed_signature, MPQ_STRONG_SIGNATURE_SIZE, padded_digest, MPQ_STRONG_SIGNATURE_SIZE, &result, &key) != CRYPT_OK)
- return ERROR_VERIFY_FAILED;
-
- // Free the key and return result
- rsa_free(&key);
- return result ? ERROR_STRONG_SIGNATURE_OK : ERROR_STRONG_SIGNATURE_ERROR;
-}
-
-static DWORD VerifyStrongSignature(
- TMPQArchive * ha,
- PMPQ_SIGNATURE_INFO pSI)
-{
- unsigned char reversed_signature[MPQ_STRONG_SIGNATURE_SIZE];
- unsigned char Sha1Digest_tail0[SHA1_DIGEST_SIZE];
- unsigned char Sha1Digest_tail1[SHA1_DIGEST_SIZE];
- unsigned char Sha1Digest_tail2[SHA1_DIGEST_SIZE];
- unsigned char padded_digest[MPQ_STRONG_SIGNATURE_SIZE];
- DWORD dwResult;
- size_t digest_offset;
-
- // Calculate SHA1 hash of the archive
- if(!CalculateMpqHashSha1(ha, pSI, Sha1Digest_tail0, Sha1Digest_tail1, Sha1Digest_tail2))
- return ERROR_VERIFY_FAILED;
-
- // Prepare the signature for decryption
- memcpy(reversed_signature, &pSI->Signature[4], MPQ_STRONG_SIGNATURE_SIZE);
- memrev(reversed_signature, MPQ_STRONG_SIGNATURE_SIZE);
-
- // Prepare the padded digest for comparison
- digest_offset = sizeof(padded_digest) - SHA1_DIGEST_SIZE;
- memset(padded_digest, 0xbb, digest_offset);
- padded_digest[0] = 0x0b;
-
- // Try Blizzard Strong public key with no SHA1 tail
- memcpy(padded_digest + digest_offset, Sha1Digest_tail0, SHA1_DIGEST_SIZE);
- memrev(padded_digest + digest_offset, SHA1_DIGEST_SIZE);
- dwResult = VerifyStrongSignatureWithKey(reversed_signature, padded_digest, szBlizzardStrongPublicKey);
- if(dwResult == ERROR_STRONG_SIGNATURE_OK)
- return dwResult;
-
- // Try War 3 map public key with plain file name as SHA1 tail
- memcpy(padded_digest + digest_offset, Sha1Digest_tail1, SHA1_DIGEST_SIZE);
- memrev(padded_digest + digest_offset, SHA1_DIGEST_SIZE);
- dwResult = VerifyStrongSignatureWithKey(reversed_signature, padded_digest, szWarcraft3MapPublicKey);
- if(dwResult == ERROR_STRONG_SIGNATURE_OK)
- return dwResult;
-
- // Try WoW-TBC public key with "ARCHIVE" as SHA1 tail
- memcpy(padded_digest + digest_offset, Sha1Digest_tail2, SHA1_DIGEST_SIZE);
- memrev(padded_digest + digest_offset, SHA1_DIGEST_SIZE);
- dwResult = VerifyStrongSignatureWithKey(reversed_signature, padded_digest, szWowPatchPublicKey);
- if(dwResult == ERROR_STRONG_SIGNATURE_OK)
- return dwResult;
-
- // Try Survey public key with no SHA1 tail
- memcpy(padded_digest + digest_offset, Sha1Digest_tail0, SHA1_DIGEST_SIZE);
- memrev(padded_digest + digest_offset, SHA1_DIGEST_SIZE);
- dwResult = VerifyStrongSignatureWithKey(reversed_signature, padded_digest, szWowSurveyPublicKey);
- if(dwResult == ERROR_STRONG_SIGNATURE_OK)
- return dwResult;
-
- // Try Starcraft II public key with no SHA1 tail
- memcpy(padded_digest + digest_offset, Sha1Digest_tail0, SHA1_DIGEST_SIZE);
- memrev(padded_digest + digest_offset, SHA1_DIGEST_SIZE);
- dwResult = VerifyStrongSignatureWithKey(reversed_signature, padded_digest, szStarcraft2MapPublicKey);
- if(dwResult == ERROR_STRONG_SIGNATURE_OK)
- return dwResult;
-
- return ERROR_STRONG_SIGNATURE_ERROR;
-}
-
-static DWORD VerifyFile(
- HANDLE hMpq,
- const char * szFileName,
- LPDWORD pdwCrc32,
- char * pMD5,
- DWORD dwFlags)
-{
- hash_state md5_state;
- unsigned char * pFileMd5;
- unsigned char md5[MD5_DIGEST_SIZE];
- TFileEntry * pFileEntry;
- TMPQFile * hf;
- BYTE Buffer[0x1000];
- HANDLE hFile = NULL;
- DWORD dwVerifyResult = 0;
- DWORD dwTotalBytes = 0;
- DWORD dwCrc32 = 0;
-
- //
- // Note: When the MPQ is patched, it will
- // automatically check the patched version of the file
- //
-
- // Make sure the md5 is initialized
- memset(md5, 0, sizeof(md5));
-
- // If we have to verify raw data MD5, do it before file open
- if(dwFlags & SFILE_VERIFY_RAW_MD5)
- {
- TMPQArchive * ha = (TMPQArchive *)hMpq;
-
- // Parse the base MPQ and all patches
- while(ha != NULL)
- {
- // Does the archive have support for raw MD5?
- if(ha->pHeader->dwRawChunkSize != 0)
- {
- // The file has raw MD5 if the archive supports it
- dwVerifyResult |= VERIFY_FILE_HAS_RAW_MD5;
-
- // Find file entry for the file
- pFileEntry = GetFileEntryLocale(ha, szFileName, lcFileLocale);
- if(pFileEntry != NULL)
- {
- // If the file's raw MD5 doesn't match, don't bother with more checks
- if(VerifyRawMpqData(ha, pFileEntry->ByteOffset, pFileEntry->dwCmpSize) != ERROR_SUCCESS)
- return dwVerifyResult | VERIFY_FILE_RAW_MD5_ERROR;
- }
- }
-
- // Move to the next patch
- ha = ha->haPatch;
- }
- }
-
- // Attempt to open the file
- if(SFileOpenFileEx(hMpq, szFileName, SFILE_OPEN_FROM_MPQ, &hFile))
- {
- // Get the file size
- hf = (TMPQFile *)hFile;
- pFileEntry = hf->pFileEntry;
- dwTotalBytes = SFileGetFileSize(hFile, NULL);
-
- // Initialize the CRC32 and MD5 contexts
- md5_init(&md5_state);
- dwCrc32 = crc32(0, Z_NULL, 0);
-
- // Also turn on sector checksum verification
- if(dwFlags & SFILE_VERIFY_SECTOR_CRC)
- hf->bCheckSectorCRCs = true;
-
- // Go through entire file and update both CRC32 and MD5
- for(;;)
- {
- DWORD dwBytesRead = 0;
-
- // Read data from file
- SFileReadFile(hFile, Buffer, sizeof(Buffer), &dwBytesRead, NULL);
- if(dwBytesRead == 0)
- {
- if(GetLastError() == ERROR_CHECKSUM_ERROR)
- dwVerifyResult |= VERIFY_FILE_SECTOR_CRC_ERROR;
- break;
- }
-
- // Update CRC32 value
- if(dwFlags & SFILE_VERIFY_FILE_CRC)
- dwCrc32 = crc32(dwCrc32, Buffer, dwBytesRead);
-
- // Update MD5 value
- if(dwFlags & SFILE_VERIFY_FILE_MD5)
- md5_process(&md5_state, Buffer, dwBytesRead);
-
- // Decrement the total size
- dwTotalBytes -= dwBytesRead;
- }
-
- // If the file has sector checksums, indicate it in the flags
- if(dwFlags & SFILE_VERIFY_SECTOR_CRC)
- {
- if((hf->pFileEntry->dwFlags & MPQ_FILE_SECTOR_CRC) && hf->SectorChksums != NULL && hf->SectorChksums[0] != 0)
- dwVerifyResult |= VERIFY_FILE_HAS_SECTOR_CRC;
- }
-
- // Check if the entire file has been read
- // No point in checking CRC32 and MD5 if not
- // Skip checksum checks if the file has patches
- if(dwTotalBytes == 0)
- {
- // Check CRC32 and MD5 only if there is no patches
- if(hf->hfPatch == NULL)
- {
- // Check if the CRC32 matches.
- if(dwFlags & SFILE_VERIFY_FILE_CRC)
- {
- // Only check the CRC32 if it is valid
- if(pFileEntry->dwCrc32 != 0)
- {
- dwVerifyResult |= VERIFY_FILE_HAS_CHECKSUM;
- if(dwCrc32 != pFileEntry->dwCrc32)
- dwVerifyResult |= VERIFY_FILE_CHECKSUM_ERROR;
- }
- }
-
- // Check if MD5 matches
- if(dwFlags & SFILE_VERIFY_FILE_MD5)
- {
- // Patch files have their MD5 saved in the patch info
- pFileMd5 = (hf->pPatchInfo != NULL) ? hf->pPatchInfo->md5 : pFileEntry->md5;
- md5_done(&md5_state, md5);
-
- // Only check the MD5 if it is valid
- if(IsValidMD5(pFileMd5))
- {
- dwVerifyResult |= VERIFY_FILE_HAS_MD5;
- if(memcmp(md5, pFileMd5, MD5_DIGEST_SIZE))
- dwVerifyResult |= VERIFY_FILE_MD5_ERROR;
- }
- }
- }
- else
- {
- // Patched files are MD5-checked automatically
- dwVerifyResult |= VERIFY_FILE_HAS_MD5;
- }
- }
- else
- {
- dwVerifyResult |= VERIFY_READ_ERROR;
- }
-
- SFileCloseFile(hFile);
- }
- else
- {
- // Remember that the file couldn't be open
- dwVerifyResult |= VERIFY_OPEN_ERROR;
- }
-
- // If the caller required CRC32 and/or MD5, give it to him
- if(pdwCrc32 != NULL)
- *pdwCrc32 = dwCrc32;
- if(pMD5 != NULL)
- memcpy(pMD5, md5, MD5_DIGEST_SIZE);
-
- return dwVerifyResult;
-}
-
-// Used in SFileGetFileInfo
-bool QueryMpqSignatureInfo(
- TMPQArchive * ha,
- PMPQ_SIGNATURE_INFO pSI)
-{
- TFileEntry * pFileEntry;
- ULONGLONG ExtraBytes;
- DWORD dwFileSize;
-
- // Make sure it's all zeroed
- memset(pSI, 0, sizeof(MPQ_SIGNATURE_INFO));
-
- // Calculate the range of the MPQ
- CalculateArchiveRange(ha, pSI);
-
- // If there is "(signature)" file in the MPQ, it has a weak signature
- pFileEntry = GetFileEntryLocale(ha, SIGNATURE_NAME, LANG_NEUTRAL);
- if(pFileEntry != NULL)
- {
- // Calculate the begin and end of the signature file itself
- pSI->BeginExclude = ha->MpqPos + pFileEntry->ByteOffset;
- pSI->EndExclude = pSI->BeginExclude + pFileEntry->dwCmpSize;
- dwFileSize = (DWORD)(pSI->EndExclude - pSI->BeginExclude);
-
- // Does the signature have proper size?
- if(dwFileSize == MPQ_SIGNATURE_FILE_SIZE)
- {
- // Read the weak signature
- if(!FileStream_Read(ha->pStream, &pSI->BeginExclude, pSI->Signature, dwFileSize))
- return false;
-
- pSI->SignatureTypes |= SIGNATURE_TYPE_WEAK;
- pSI->cbSignatureSize = dwFileSize;
- return true;
- }
- }
-
- // If there is extra bytes beyond the end of the archive,
- // it's the strong signature
- ExtraBytes = pSI->EndOfFile - pSI->EndMpqData;
- if(ExtraBytes >= (MPQ_STRONG_SIGNATURE_SIZE + 4))
- {
- // Read the strong signature
- if(!FileStream_Read(ha->pStream, &pSI->EndMpqData, pSI->Signature, (MPQ_STRONG_SIGNATURE_SIZE + 4)))
- return false;
-
- // Check the signature header "NGIS"
- if(pSI->Signature[0] != 'N' || pSI->Signature[1] != 'G' || pSI->Signature[2] != 'I' || pSI->Signature[3] != 'S')
- return false;
-
- pSI->SignatureTypes |= SIGNATURE_TYPE_STRONG;
- return true;
- }
-
- // Succeeded, but no known signature found
- return true;
-}
-
-//-----------------------------------------------------------------------------
-// Support for weak signature
-
-int SSignFileCreate(TMPQArchive * ha)
-{
- TMPQFile * hf = NULL;
- BYTE EmptySignature[MPQ_SIGNATURE_FILE_SIZE];
- int nError = ERROR_SUCCESS;
-
- // Only save the signature if we should do so
- if(ha->dwFileFlags3 != 0)
- {
- // The (signature) file must be non-encrypted and non-compressed
- assert(ha->dwFlags & MPQ_FLAG_SIGNATURE_NEW);
- assert(ha->dwFileFlags3 == MPQ_FILE_EXISTS);
- assert(ha->dwReservedFiles > 0);
-
- // Create the (signature) file file in the MPQ
- // Note that the file must not be compressed or encrypted
- nError = SFileAddFile_Init(ha, SIGNATURE_NAME,
- 0,
- sizeof(EmptySignature),
- LANG_NEUTRAL,
- ha->dwFileFlags3 | MPQ_FILE_REPLACEEXISTING,
- &hf);
-
- // Write the empty signature file to the archive
- if(nError == ERROR_SUCCESS)
- {
- // Write the empty zeroed file to the MPQ
- memset(EmptySignature, 0, sizeof(EmptySignature));
- nError = SFileAddFile_Write(hf, EmptySignature, (DWORD)sizeof(EmptySignature), 0);
- SFileAddFile_Finish(hf);
-
- // Clear the invalid mark
- ha->dwFlags &= ~(MPQ_FLAG_SIGNATURE_NEW | MPQ_FLAG_SIGNATURE_NONE);
- ha->dwReservedFiles--;
- }
- }
-
- return nError;
-}
-
-int SSignFileFinish(TMPQArchive * ha)
-{
- MPQ_SIGNATURE_INFO si;
- unsigned long signature_len = MPQ_WEAK_SIGNATURE_SIZE;
- BYTE WeakSignature[MPQ_SIGNATURE_FILE_SIZE];
- BYTE Md5Digest[MD5_DIGEST_SIZE];
- rsa_key key;
- int hash_idx = find_hash("md5");
-
- // Sanity checks
- assert((ha->dwFlags & MPQ_FLAG_CHANGED) == 0);
- assert(ha->dwFileFlags3 == MPQ_FILE_EXISTS);
-
- // Query the weak signature info
- memset(&si, 0, sizeof(MPQ_SIGNATURE_INFO));
- if(!QueryMpqSignatureInfo(ha, &si))
- return ERROR_FILE_CORRUPT;
-
- // There must be exactly one signature
- if(si.SignatureTypes != SIGNATURE_TYPE_WEAK)
- return ERROR_FILE_CORRUPT;
-
- // Calculate MD5 of the entire archive
- if(!CalculateMpqHashMd5(ha, &si, Md5Digest))
- return ERROR_VERIFY_FAILED;
-
- // Decode the private key
- if(!decode_base64_key(szBlizzardWeakPrivateKey, &key))
- return ERROR_VERIFY_FAILED;
-
- // Sign the hash
- memset(WeakSignature, 0, sizeof(WeakSignature));
- rsa_sign_hash_ex(Md5Digest, sizeof(Md5Digest), WeakSignature + 8, &signature_len, LTC_LTC_PKCS_1_V1_5, 0, 0, hash_idx, 0, &key);
- memrev(WeakSignature + 8, MPQ_WEAK_SIGNATURE_SIZE);
- rsa_free(&key);
-
- // Write the signature to the MPQ. Don't use SFile* functions, but write the hash directly
- if(!FileStream_Write(ha->pStream, &si.BeginExclude, WeakSignature, MPQ_SIGNATURE_FILE_SIZE))
- return GetLastError();
-
- return ERROR_SUCCESS;
-}
-
-//-----------------------------------------------------------------------------
-// Public (exported) functions
-
-bool WINAPI SFileGetFileChecksums(HANDLE hMpq, const char * szFileName, LPDWORD pdwCrc32, char * pMD5)
-{
- DWORD dwVerifyResult;
- DWORD dwVerifyFlags = 0;
-
- if(pdwCrc32 != NULL)
- dwVerifyFlags |= SFILE_VERIFY_FILE_CRC;
- if(pMD5 != NULL)
- dwVerifyFlags |= SFILE_VERIFY_FILE_MD5;
-
- dwVerifyResult = VerifyFile(hMpq,
- szFileName,
- pdwCrc32,
- pMD5,
- dwVerifyFlags);
-
- // If verification failed, return zero
- if(dwVerifyResult & VERIFY_FILE_ERROR_MASK)
- {
- SetLastError(ERROR_FILE_CORRUPT);
- return false;
- }
-
- return true;
-}
-
-
-DWORD WINAPI SFileVerifyFile(HANDLE hMpq, const char * szFileName, DWORD dwFlags)
-{
- return VerifyFile(hMpq,
- szFileName,
- NULL,
- NULL,
- dwFlags);
-}
-
-// Verifies raw data of the archive Only works for MPQs version 4 or newer
-int WINAPI SFileVerifyRawData(HANDLE hMpq, DWORD dwWhatToVerify, const char * szFileName)
-{
- TMPQArchive * ha = (TMPQArchive *)hMpq;
- TFileEntry * pFileEntry;
- TMPQHeader * pHeader;
-
- // Verify input parameters
- if(!IsValidMpqHandle(hMpq))
- return ERROR_INVALID_PARAMETER;
- pHeader = ha->pHeader;
-
- // If the archive doesn't have raw data MD5, report it as OK
- if(pHeader->dwRawChunkSize == 0)
- return ERROR_SUCCESS;
-
- // If we have to verify MPQ header, do it
- switch(dwWhatToVerify)
- {
- case SFILE_VERIFY_MPQ_HEADER:
-
- // Only if the header is of version 4 or newer
- if(pHeader->dwHeaderSize >= (MPQ_HEADER_SIZE_V4 - MD5_DIGEST_SIZE))
- return VerifyRawMpqData(ha, 0, MPQ_HEADER_SIZE_V4 - MD5_DIGEST_SIZE);
- return ERROR_SUCCESS;
-
- case SFILE_VERIFY_HET_TABLE:
-
- // Only if we have HET table
- if(pHeader->HetTablePos64 && pHeader->HetTableSize64)
- return VerifyRawMpqData(ha, pHeader->HetTablePos64, (DWORD)pHeader->HetTableSize64);
- return ERROR_SUCCESS;
-
- case SFILE_VERIFY_BET_TABLE:
-
- // Only if we have BET table
- if(pHeader->BetTablePos64 && pHeader->BetTableSize64)
- return VerifyRawMpqData(ha, pHeader->BetTablePos64, (DWORD)pHeader->BetTableSize64);
- return ERROR_SUCCESS;
-
- case SFILE_VERIFY_HASH_TABLE:
-
- // Hash table is not protected by MD5
- return ERROR_SUCCESS;
-
- case SFILE_VERIFY_BLOCK_TABLE:
-
- // Block table is not protected by MD5
- return ERROR_SUCCESS;
-
- case SFILE_VERIFY_HIBLOCK_TABLE:
-
- // It is unknown if the hi-block table is protected my MD5 or not.
- return ERROR_SUCCESS;
-
- case SFILE_VERIFY_FILE:
-
- // Verify parameters
- if(szFileName == NULL || *szFileName == 0)
- return ERROR_INVALID_PARAMETER;
-
- // Get the offset of a file
- pFileEntry = GetFileEntryLocale(ha, szFileName, lcFileLocale);
- if(pFileEntry == NULL)
- return ERROR_FILE_NOT_FOUND;
-
- return VerifyRawMpqData(ha, pFileEntry->ByteOffset, pFileEntry->dwCmpSize);
- }
-
- return ERROR_INVALID_PARAMETER;
-}
-
-
-// Verifies the archive against the signature
-DWORD WINAPI SFileVerifyArchive(HANDLE hMpq)
-{
- MPQ_SIGNATURE_INFO si;
- TMPQArchive * ha = (TMPQArchive *)hMpq;
-
- // Verify input parameters
- if(!IsValidMpqHandle(hMpq))
- return ERROR_VERIFY_FAILED;
-
- // If the archive was modified, we need to flush it
- if(ha->dwFlags & MPQ_FLAG_CHANGED)
- SFileFlushArchive(hMpq);
-
- // Get the MPQ signature and signature type
- memset(&si, 0, sizeof(MPQ_SIGNATURE_INFO));
- if(!QueryMpqSignatureInfo(ha, &si))
- return ERROR_VERIFY_FAILED;
-
- // If there is no signature
- if(si.SignatureTypes == 0)
- return ERROR_NO_SIGNATURE;
-
- // We haven't seen a MPQ with both signatures
- assert(si.SignatureTypes == SIGNATURE_TYPE_WEAK || si.SignatureTypes == SIGNATURE_TYPE_STRONG);
-
- // Verify the strong signature, if present
- if(si.SignatureTypes & SIGNATURE_TYPE_STRONG)
- return VerifyStrongSignature(ha, &si);
-
- // Verify the weak signature, if present
- if(si.SignatureTypes & SIGNATURE_TYPE_WEAK)
- return VerifyWeakSignature(ha, &si);
-
- return ERROR_NO_SIGNATURE;
-}
-
-// Verifies the archive against the signature
-bool WINAPI SFileSignArchive(HANDLE hMpq, DWORD dwSignatureType)
-{
- TMPQArchive * ha;
-
- // Verify the archive handle
- ha = IsValidMpqHandle(hMpq);
- if(ha == NULL)
- {
- SetLastError(ERROR_INVALID_PARAMETER);
- return false;
- }
-
- // We only support weak signature, and only for MPQs version 1.0
- if(dwSignatureType != SIGNATURE_TYPE_WEAK)
- {
- SetLastError(ERROR_INVALID_PARAMETER);
- return false;
- }
-
- // The archive must not be malformed and must not be read-only
- if(ha->dwFlags & (MPQ_FLAG_READ_ONLY | MPQ_FLAG_MALFORMED))
- {
- SetLastError(ERROR_ACCESS_DENIED);
- return false;
- }
-
- // If the signature is not there yet
- if(ha->dwFileFlags3 == 0)
- {
- // Turn the signature on. The signature will
- // be applied when the archive is closed
- ha->dwFlags |= MPQ_FLAG_SIGNATURE_NEW | MPQ_FLAG_CHANGED;
- ha->dwFileFlags3 = MPQ_FILE_EXISTS;
- ha->dwReservedFiles++;
- }
-
- return true;
-}
-
+/*****************************************************************************/
+/* SFileVerify.cpp Copyright (c) Ladislav Zezula 2010 */
+/*---------------------------------------------------------------------------*/
+/* MPQ files and MPQ archives verification. */
+/* */
+/* The MPQ signature verification has been written by Jean-Francois Roy */
+/* <bahamut@macstorm.org> and Justin Olbrantz (Quantam). */
+/* The MPQ public keys have been created by MPQKit, using OpenSSL library. */
+/* */
+/*---------------------------------------------------------------------------*/
+/* Date Ver Who Comment */
+/* -------- ---- --- ------- */
+/* 04.05.10 1.00 Lad The first version of SFileVerify.cpp */
+/*****************************************************************************/
+
+#define __STORMLIB_SELF__
+#include "StormLib.h"
+#include "StormCommon.h"
+
+//-----------------------------------------------------------------------------
+// Local defines
+
+#define MPQ_DIGEST_UNIT_SIZE 0x10000
+
+//-----------------------------------------------------------------------------
+// Known Blizzard public keys
+// Created by Jean-Francois Roy using OpenSSL
+
+static const char * szBlizzardWeakPrivateKey =
+ "-----BEGIN PRIVATE KEY-----"
+ "MIIBOQIBAAJBAJJidwS/uILMBSO5DLGsBFknIXWWjQJe2kfdfEk3G/j66w4KkhZ1"
+ "V61Rt4zLaMVCYpDun7FLwRjkMDSepO1q2DcCAwEAAQJANtiztVDMJh2hE1hjPDKy"
+ "UmEJ9U/aN3gomuKOjbQbQ/bWWcM/WfhSVHmPqtqh/bQI2UXFr0rnXngeteZHLr/b"
+ "8QIhAMuWriSKGMACw18/rVVfUrThs915odKBH1Alr3vMVVzZAiEAuBHPSQkgwcb6"
+ "L4MWaiKuOzq08mSyNqPeN8oSy18q848CIHeMn+3s+eOmu7su1UYQl6yH7OrdBd1q"
+ "3UxfFNEJiAbhAiAqxdCyOxHGlbM7aS3DOg3cq5ayoN2cvtV7h1R4t8OmVwIgF+5z"
+ "/6vkzBUsZhd8Nwyis+MeQYH0rpFpMKdTlqmPF2Q="
+ "-----END PRIVATE KEY-----";
+
+static const char * szBlizzardWeakPublicKey =
+ "-----BEGIN PUBLIC KEY-----"
+ "MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAJJidwS/uILMBSO5DLGsBFknIXWWjQJe"
+ "2kfdfEk3G/j66w4KkhZ1V61Rt4zLaMVCYpDun7FLwRjkMDSepO1q2DcCAwEAAQ=="
+ "-----END PUBLIC KEY-----";
+
+static const char * szBlizzardStrongPublicKey =
+ "-----BEGIN PUBLIC KEY-----"
+ "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAsQZ+ziT2h8h+J/iMQpgd"
+ "tH1HaJzOBE3agjU4yMPcrixaPOZoA4t8bwfey7qczfWywocYo3pleytFF+IuD4HD"
+ "Fl9OXN1SFyupSgMx1EGZlgbFAomnbq9MQJyMqQtMhRAjFgg4TndS7YNb+JMSAEKp"
+ "kXNqY28n/EVBHD5TsMuVCL579gIenbr61dI92DDEdy790IzIG0VKWLh/KOTcTJfm"
+ "Ds/7HQTkGouVW+WUsfekuqNQo7ND9DBnhLjLjptxeFE2AZqYcA1ao3S9LN3GL1tW"
+ "lVXFIX9c7fWqaVTQlZ2oNsI/ARVApOK3grNgqvwH6YoVYVXjNJEo5sQJsPsdV/hk"
+ "dwIDAQAB"
+ "-----END PUBLIC KEY-----";
+
+static const char * szWarcraft3MapPublicKey =
+ "-----BEGIN PUBLIC KEY-----"
+ "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA1BwklUUQ3UvjizOBRoF5"
+ "yyOVc7KD+oGOQH5i6eUk1yfs0luCC70kNucNrfqhmviywVtahRse1JtXCPrx2bd3"
+ "iN8Dx91fbkxjYIOGTsjYoHKTp0BbaFkJih776fcHgnFSb+7mJcDuJVvJOXxEH6w0"
+ "1vo6VtujCqj1arqbyoal+xtAaczF3us5cOEp45sR1zAWTn1+7omN7VWV4QqJPaDS"
+ "gBSESc0l1grO0i1VUSumayk7yBKIkb+LBvcG6WnYZHCi7VdLmaxER5m8oZfER66b"
+ "heHoiSQIZf9PAY6Guw2DT5BTc54j/AaLQAKf2qcRSgQLVo5kQaddF3rCpsXoB/74"
+ "6QIDAQAB"
+ "-----END PUBLIC KEY-----";
+
+static const char * szWowPatchPublicKey =
+ "-----BEGIN PUBLIC KEY-----"
+ "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwOsMV0LagAWPEtEQM6b9"
+ "6FHFkUyGbbyda2/Dfc9dyl21E9QvX+Yw7qKRMAKPzA2TlQQLZKvXpnKXF/YIK5xa"
+ "5uwg9CEHCEAYolLG4xn0FUOE0E/0PuuytI0p0ICe6rk00PifZzTr8na2wI/l/GnQ"
+ "bvnIVF1ck6cslATpQJ5JJVMXzoFlUABS19WESw4MXuJAS3AbMhxNWdEhVv7eO51c"
+ "yGjRLy9QjogZODZTY0fSEksgBqQxNCoYVJYI/sF5K2flDsGqrIp0OdJ6teJlzg1Y"
+ "UjYnb6bKjlidXoHEXI2TgA/mD6O3XFIt08I9s3crOCTgICq7cgX35qrZiIVWZdRv"
+ "TwIDAQAB"
+ "-----END PUBLIC KEY-----";
+
+static const char * szWowSurveyPublicKey =
+ "-----BEGIN PUBLIC KEY-----"
+ "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAnIt1DR6nRyyKsy2qahHe"
+ "MKLtacatn/KxieHcwH87wLBxKy+jZ0gycTmJ7SaTdBAEMDs/V5IPIXEtoqYnid2c"
+ "63TmfGDU92oc3Ph1PWUZ2PWxBhT06HYxRdbrgHw9/I29pNPi/607x+lzPORITOgU"
+ "BR6MR8au8HsQP4bn4vkJNgnSgojh48/XQOB/cAln7As1neP61NmVimoLR4Bwi3zt"
+ "zfgrZaUpyeNCUrOYJmH09YIjbBySTtXOUidoPHjFrMsCWpr6xs8xbETbs7MJFL6a"
+ "vcUfTT67qfIZ9RsuKfnXJTIrV0kwDSjjuNXiPTmWAehSsiHIsrUXX5RNcwsSjClr"
+ "nQIDAQAB"
+ "-----END PUBLIC KEY-----";
+
+static const char * szStarcraft2MapPublicKey =
+ "-----BEGIN PUBLIC KEY-----"
+ "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAmk4GT8zb+ICC25a17KZB"
+ "q/ygKGJ2VSO6IT5PGHJlm1KfnHBA4B6SH3xMlJ4c6eG2k7QevZv+FOhjsAHubyWq"
+ "2VKqWbrIFKv2ILc2RfMn8J9EDVRxvcxh6slRrVL69D0w1tfVGjMiKq2Fym5yGoRT"
+ "E7CRgDqbAbXP9LBsCNWHiJLwfxMGzHbk8pIl9oia5pvM7ofZamSHchxlpy6xa4GJ"
+ "7xKN01YCNvklTL1D7uol3wkwcHc7vrF8QwuJizuA5bSg4poEGtH62BZOYi+UL/z0"
+ "31YK+k9CbQyM0X0pJoJoYz1TK+Y5J7vBnXCZtfcTYQ/ZzN6UcxTa57dJaiOlCh9z"
+ "nQIDAQAB"
+ "-----END PUBLIC KEY-----";
+
+//-----------------------------------------------------------------------------
+// Local functions
+
+static void memrev(unsigned char *buf, size_t count)
+{
+ unsigned char *r;
+
+ for (r = buf + count - 1; buf < r; buf++, r--)
+ {
+ *buf ^= *r;
+ *r ^= *buf;
+ *buf ^= *r;
+ }
+}
+
+static bool decode_base64_key(const char * szKeyBase64, rsa_key * key)
+{
+ unsigned char decoded_key[0x200];
+ const char * szBase64Begin;
+ const char * szBase64End;
+ unsigned long decoded_length = sizeof(decoded_key);
+ unsigned long length;
+
+ // Find out the begin of the BASE64 data
+ szBase64Begin = szKeyBase64 + strlen("-----BEGIN PUBLIC KEY-----");
+ szBase64End = szBase64Begin + strlen(szBase64Begin) - strlen("-----END PUBLIC KEY-----");
+ if(szBase64End[0] != '-')
+ return false;
+
+ // decode the base64 string
+ length = (unsigned long)(szBase64End - szBase64Begin);
+ if(base64_decode((unsigned char *)szBase64Begin, length, decoded_key, &decoded_length) != CRYPT_OK)
+ return false;
+
+ // Create RSA key
+ if(rsa_import(decoded_key, decoded_length, key) != CRYPT_OK)
+ return false;
+
+ return true;
+}
+
+static void GetPlainAnsiFileName(
+ const TCHAR * szFileName,
+ char * szPlainName)
+{
+ const TCHAR * szPlainNameT = GetPlainFileName(szFileName);
+
+ // Convert the plain name to ANSI
+ while(*szPlainNameT != 0)
+ *szPlainName++ = (char)*szPlainNameT++;
+ *szPlainName = 0;
+}
+
+// Calculate begin and end of the MPQ archive
+static void CalculateArchiveRange(
+ TMPQArchive * ha,
+ PMPQ_SIGNATURE_INFO pSI)
+{
+ ULONGLONG TempPos = 0;
+ char szMapHeader[0x200];
+
+ // Get the MPQ begin
+ pSI->BeginMpqData = ha->MpqPos;
+
+ // Warcraft III maps are signed from the map header to the end
+ if(FileStream_Read(ha->pStream, &TempPos, szMapHeader, sizeof(szMapHeader)))
+ {
+ // Is it a map header ?
+ if(szMapHeader[0] == 'H' && szMapHeader[1] == 'M' && szMapHeader[2] == '3' && szMapHeader[3] == 'W')
+ {
+ // We will have to hash since the map header
+ pSI->BeginMpqData = 0;
+ }
+ }
+
+ // Get the MPQ data end. This is stored in the MPQ header
+ pSI->EndMpqData = ha->MpqPos + ha->pHeader->ArchiveSize64;
+
+ // Get the size of the entire file
+ FileStream_GetSize(ha->pStream, &pSI->EndOfFile);
+}
+
+static bool CalculateMpqHashMd5(
+ TMPQArchive * ha,
+ PMPQ_SIGNATURE_INFO pSI,
+ LPBYTE pMd5Digest)
+{
+ hash_state md5_state;
+ ULONGLONG BeginBuffer;
+ ULONGLONG EndBuffer;
+ LPBYTE pbDigestBuffer = NULL;
+
+ // Allocate buffer for creating the MPQ digest.
+ pbDigestBuffer = STORM_ALLOC(BYTE, MPQ_DIGEST_UNIT_SIZE);
+ if(pbDigestBuffer == NULL)
+ return false;
+
+ // Initialize the MD5 hash state
+ md5_init(&md5_state);
+
+ // Set the byte offset of begin of the data
+ BeginBuffer = pSI->BeginMpqData;
+
+ // Create the digest
+ for(;;)
+ {
+ ULONGLONG BytesRemaining;
+ LPBYTE pbSigBegin = NULL;
+ LPBYTE pbSigEnd = NULL;
+ DWORD dwToRead = MPQ_DIGEST_UNIT_SIZE;
+
+ // Check the number of bytes remaining
+ BytesRemaining = pSI->EndMpqData - BeginBuffer;
+ if(BytesRemaining < MPQ_DIGEST_UNIT_SIZE)
+ dwToRead = (DWORD)BytesRemaining;
+ if(dwToRead == 0)
+ break;
+
+ // Read the next chunk
+ if(!FileStream_Read(ha->pStream, &BeginBuffer, pbDigestBuffer, dwToRead))
+ {
+ STORM_FREE(pbDigestBuffer);
+ return false;
+ }
+
+ // Move the current byte offset
+ EndBuffer = BeginBuffer + dwToRead;
+
+ // Check if the signature is within the loaded digest
+ if(BeginBuffer <= pSI->BeginExclude && pSI->BeginExclude < EndBuffer)
+ pbSigBegin = pbDigestBuffer + (size_t)(pSI->BeginExclude - BeginBuffer);
+ if(BeginBuffer <= pSI->EndExclude && pSI->EndExclude < EndBuffer)
+ pbSigEnd = pbDigestBuffer + (size_t)(pSI->EndExclude - BeginBuffer);
+
+ // Zero the part that belongs to the signature
+ if(pbSigBegin != NULL || pbSigEnd != NULL)
+ {
+ if(pbSigBegin == NULL)
+ pbSigBegin = pbDigestBuffer;
+ if(pbSigEnd == NULL)
+ pbSigEnd = pbDigestBuffer + dwToRead;
+
+ memset(pbSigBegin, 0, (pbSigEnd - pbSigBegin));
+ }
+
+ // Pass the buffer to the hashing function
+ md5_process(&md5_state, pbDigestBuffer, dwToRead);
+
+ // Move pointers
+ BeginBuffer += dwToRead;
+ }
+
+ // Finalize the MD5 hash
+ md5_done(&md5_state, pMd5Digest);
+ STORM_FREE(pbDigestBuffer);
+ return true;
+}
+
+static void AddTailToSha1(
+ hash_state * psha1_state,
+ const char * szTail)
+{
+ unsigned char * pbTail = (unsigned char *)szTail;
+ unsigned char szUpperCase[0x200];
+ unsigned long nLength = 0;
+
+ // Convert the tail to uppercase
+ // Note that we don't need to terminate the string with zero
+ while(*pbTail != 0)
+ {
+ szUpperCase[nLength++] = AsciiToUpperTable[*pbTail++];
+ }
+
+ // Append the tail to the SHA1
+ sha1_process(psha1_state, szUpperCase, nLength);
+}
+
+static bool CalculateMpqHashSha1(
+ TMPQArchive * ha,
+ PMPQ_SIGNATURE_INFO pSI,
+ unsigned char * sha1_tail0,
+ unsigned char * sha1_tail1,
+ unsigned char * sha1_tail2)
+{
+ ULONGLONG BeginBuffer;
+ hash_state sha1_state_temp;
+ hash_state sha1_state;
+ LPBYTE pbDigestBuffer = NULL;
+ char szPlainName[MAX_PATH];
+
+ // Allocate buffer for creating the MPQ digest.
+ pbDigestBuffer = STORM_ALLOC(BYTE, MPQ_DIGEST_UNIT_SIZE);
+ if(pbDigestBuffer == NULL)
+ return false;
+
+ // Initialize SHA1 state structure
+ sha1_init(&sha1_state);
+
+ // Calculate begin of data to be hashed
+ BeginBuffer = pSI->BeginMpqData;
+
+ // Create the digest
+ for(;;)
+ {
+ ULONGLONG BytesRemaining;
+ DWORD dwToRead = MPQ_DIGEST_UNIT_SIZE;
+
+ // Check the number of bytes remaining
+ BytesRemaining = pSI->EndMpqData - BeginBuffer;
+ if(BytesRemaining < MPQ_DIGEST_UNIT_SIZE)
+ dwToRead = (DWORD)BytesRemaining;
+ if(dwToRead == 0)
+ break;
+
+ // Read the next chunk
+ if(!FileStream_Read(ha->pStream, &BeginBuffer, pbDigestBuffer, dwToRead))
+ {
+ STORM_FREE(pbDigestBuffer);
+ return false;
+ }
+
+ // Pass the buffer to the hashing function
+ sha1_process(&sha1_state, pbDigestBuffer, dwToRead);
+
+ // Move pointers
+ BeginBuffer += dwToRead;
+ }
+
+ // Add all three known tails and generate three hashes
+ memcpy(&sha1_state_temp, &sha1_state, sizeof(hash_state));
+ sha1_done(&sha1_state_temp, sha1_tail0);
+
+ memcpy(&sha1_state_temp, &sha1_state, sizeof(hash_state));
+ GetPlainAnsiFileName(FileStream_GetFileName(ha->pStream), szPlainName);
+ AddTailToSha1(&sha1_state_temp, szPlainName);
+ sha1_done(&sha1_state_temp, sha1_tail1);
+
+ memcpy(&sha1_state_temp, &sha1_state, sizeof(hash_state));
+ AddTailToSha1(&sha1_state_temp, "ARCHIVE");
+ sha1_done(&sha1_state_temp, sha1_tail2);
+
+ // Finalize the MD5 hash
+ STORM_FREE(pbDigestBuffer);
+ return true;
+}
+
+static int VerifyRawMpqData(
+ TMPQArchive * ha,
+ ULONGLONG ByteOffset,
+ DWORD dwDataSize)
+{
+ ULONGLONG DataOffset = ha->MpqPos + ByteOffset;
+ LPBYTE pbDataChunk;
+ LPBYTE pbMD5Array1; // Calculated MD5 array
+ LPBYTE pbMD5Array2; // MD5 array loaded from the MPQ
+ DWORD dwBytesInChunk;
+ DWORD dwChunkCount;
+ DWORD dwChunkSize = ha->pHeader->dwRawChunkSize;
+ DWORD dwMD5Size;
+ int nError = ERROR_SUCCESS;
+
+ // Don't verify zero-sized blocks
+ if(dwDataSize == 0)
+ return ERROR_SUCCESS;
+
+ // Get the number of data chunks to calculate MD5
+ assert(dwChunkSize != 0);
+ dwChunkCount = ((dwDataSize - 1) / dwChunkSize) + 1;
+ dwMD5Size = dwChunkCount * MD5_DIGEST_SIZE;
+
+ // Allocate space for data chunk and for the MD5 array
+ pbDataChunk = STORM_ALLOC(BYTE, dwChunkSize);
+ if(pbDataChunk == NULL)
+ return ERROR_NOT_ENOUGH_MEMORY;
+
+ // Allocate space for MD5 array
+ pbMD5Array1 = STORM_ALLOC(BYTE, dwMD5Size);
+ pbMD5Array2 = STORM_ALLOC(BYTE, dwMD5Size);
+ if(pbMD5Array1 == NULL || pbMD5Array2 == NULL)
+ nError = ERROR_NOT_ENOUGH_MEMORY;
+
+ // Calculate MD5 of each data chunk
+ if(nError == ERROR_SUCCESS)
+ {
+ LPBYTE pbMD5 = pbMD5Array1;
+
+ for(DWORD i = 0; i < dwChunkCount; i++)
+ {
+ // Get the number of bytes in the chunk
+ dwBytesInChunk = STORMLIB_MIN(dwChunkSize, dwDataSize);
+
+ // Read the data chunk
+ if(!FileStream_Read(ha->pStream, &DataOffset, pbDataChunk, dwBytesInChunk))
+ {
+ nError = ERROR_FILE_CORRUPT;
+ break;
+ }
+
+ // Calculate MD5
+ CalculateDataBlockHash(pbDataChunk, dwBytesInChunk, pbMD5);
+
+ // Move pointers and offsets
+ DataOffset += dwBytesInChunk;
+ dwDataSize -= dwBytesInChunk;
+ pbMD5 += MD5_DIGEST_SIZE;
+ }
+ }
+
+ // Read the MD5 array
+ if(nError == ERROR_SUCCESS)
+ {
+ // Read the array of MD5
+ if(!FileStream_Read(ha->pStream, &DataOffset, pbMD5Array2, dwMD5Size))
+ nError = GetLastError();
+ }
+
+ // Compare the array of MD5
+ if(nError == ERROR_SUCCESS)
+ {
+ // Compare the MD5
+ if(memcmp(pbMD5Array1, pbMD5Array2, dwMD5Size))
+ nError = ERROR_FILE_CORRUPT;
+ }
+
+ // Free memory and return result
+ if(pbMD5Array2 != NULL)
+ STORM_FREE(pbMD5Array2);
+ if(pbMD5Array1 != NULL)
+ STORM_FREE(pbMD5Array1);
+ if(pbDataChunk != NULL)
+ STORM_FREE(pbDataChunk);
+ return nError;
+}
+
+static DWORD VerifyWeakSignature(
+ TMPQArchive * ha,
+ PMPQ_SIGNATURE_INFO pSI)
+{
+ BYTE RevSignature[MPQ_WEAK_SIGNATURE_SIZE];
+ BYTE Md5Digest[MD5_DIGEST_SIZE];
+ rsa_key key;
+ int hash_idx = find_hash("md5");
+ int result = 0;
+
+ // The signature might be zeroed out. In that case, we ignore it
+ if(!IsValidSignature(pSI->Signature))
+ return ERROR_WEAK_SIGNATURE_OK;
+
+ // Calculate hash of the entire archive, skipping the (signature) file
+ if(!CalculateMpqHashMd5(ha, pSI, Md5Digest))
+ return ERROR_VERIFY_FAILED;
+
+ // Import the Blizzard key in OpenSSL format
+ if(!decode_base64_key(szBlizzardWeakPublicKey, &key))
+ return ERROR_VERIFY_FAILED;
+
+ // Verify the signature
+ memcpy(RevSignature, &pSI->Signature[8], MPQ_WEAK_SIGNATURE_SIZE);
+ memrev(RevSignature, MPQ_WEAK_SIGNATURE_SIZE);
+ rsa_verify_hash_ex(RevSignature, MPQ_WEAK_SIGNATURE_SIZE, Md5Digest, sizeof(Md5Digest), LTC_LTC_PKCS_1_V1_5, hash_idx, 0, &result, &key);
+ rsa_free(&key);
+
+ // Return the result
+ return result ? ERROR_WEAK_SIGNATURE_OK : ERROR_WEAK_SIGNATURE_ERROR;
+}
+
+static DWORD VerifyStrongSignatureWithKey(
+ unsigned char * reversed_signature,
+ unsigned char * padded_digest,
+ const char * szPublicKey)
+{
+ rsa_key key;
+ int result = 0;
+
+ // Import the Blizzard key in OpenSSL format
+ if(!decode_base64_key(szPublicKey, &key))
+ {
+ assert(false);
+ return ERROR_VERIFY_FAILED;
+ }
+
+ // Verify the signature
+ if(rsa_verify_simple(reversed_signature, MPQ_STRONG_SIGNATURE_SIZE, padded_digest, MPQ_STRONG_SIGNATURE_SIZE, &result, &key) != CRYPT_OK)
+ return ERROR_VERIFY_FAILED;
+
+ // Free the key and return result
+ rsa_free(&key);
+ return result ? ERROR_STRONG_SIGNATURE_OK : ERROR_STRONG_SIGNATURE_ERROR;
+}
+
+static DWORD VerifyStrongSignature(
+ TMPQArchive * ha,
+ PMPQ_SIGNATURE_INFO pSI)
+{
+ unsigned char reversed_signature[MPQ_STRONG_SIGNATURE_SIZE];
+ unsigned char Sha1Digest_tail0[SHA1_DIGEST_SIZE];
+ unsigned char Sha1Digest_tail1[SHA1_DIGEST_SIZE];
+ unsigned char Sha1Digest_tail2[SHA1_DIGEST_SIZE];
+ unsigned char padded_digest[MPQ_STRONG_SIGNATURE_SIZE];
+ DWORD dwResult;
+ size_t digest_offset;
+
+ // Calculate SHA1 hash of the archive
+ if(!CalculateMpqHashSha1(ha, pSI, Sha1Digest_tail0, Sha1Digest_tail1, Sha1Digest_tail2))
+ return ERROR_VERIFY_FAILED;
+
+ // Prepare the signature for decryption
+ memcpy(reversed_signature, &pSI->Signature[4], MPQ_STRONG_SIGNATURE_SIZE);
+ memrev(reversed_signature, MPQ_STRONG_SIGNATURE_SIZE);
+
+ // Prepare the padded digest for comparison
+ digest_offset = sizeof(padded_digest) - SHA1_DIGEST_SIZE;
+ memset(padded_digest, 0xbb, digest_offset);
+ padded_digest[0] = 0x0b;
+
+ // Try Blizzard Strong public key with no SHA1 tail
+ memcpy(padded_digest + digest_offset, Sha1Digest_tail0, SHA1_DIGEST_SIZE);
+ memrev(padded_digest + digest_offset, SHA1_DIGEST_SIZE);
+ dwResult = VerifyStrongSignatureWithKey(reversed_signature, padded_digest, szBlizzardStrongPublicKey);
+ if(dwResult == ERROR_STRONG_SIGNATURE_OK)
+ return dwResult;
+
+ // Try War 3 map public key with plain file name as SHA1 tail
+ memcpy(padded_digest + digest_offset, Sha1Digest_tail1, SHA1_DIGEST_SIZE);
+ memrev(padded_digest + digest_offset, SHA1_DIGEST_SIZE);
+ dwResult = VerifyStrongSignatureWithKey(reversed_signature, padded_digest, szWarcraft3MapPublicKey);
+ if(dwResult == ERROR_STRONG_SIGNATURE_OK)
+ return dwResult;
+
+ // Try WoW-TBC public key with "ARCHIVE" as SHA1 tail
+ memcpy(padded_digest + digest_offset, Sha1Digest_tail2, SHA1_DIGEST_SIZE);
+ memrev(padded_digest + digest_offset, SHA1_DIGEST_SIZE);
+ dwResult = VerifyStrongSignatureWithKey(reversed_signature, padded_digest, szWowPatchPublicKey);
+ if(dwResult == ERROR_STRONG_SIGNATURE_OK)
+ return dwResult;
+
+ // Try Survey public key with no SHA1 tail
+ memcpy(padded_digest + digest_offset, Sha1Digest_tail0, SHA1_DIGEST_SIZE);
+ memrev(padded_digest + digest_offset, SHA1_DIGEST_SIZE);
+ dwResult = VerifyStrongSignatureWithKey(reversed_signature, padded_digest, szWowSurveyPublicKey);
+ if(dwResult == ERROR_STRONG_SIGNATURE_OK)
+ return dwResult;
+
+ // Try Starcraft II public key with no SHA1 tail
+ memcpy(padded_digest + digest_offset, Sha1Digest_tail0, SHA1_DIGEST_SIZE);
+ memrev(padded_digest + digest_offset, SHA1_DIGEST_SIZE);
+ dwResult = VerifyStrongSignatureWithKey(reversed_signature, padded_digest, szStarcraft2MapPublicKey);
+ if(dwResult == ERROR_STRONG_SIGNATURE_OK)
+ return dwResult;
+
+ return ERROR_STRONG_SIGNATURE_ERROR;
+}
+
+static DWORD VerifyFile(
+ HANDLE hMpq,
+ const char * szFileName,
+ LPDWORD pdwCrc32,
+ char * pMD5,
+ DWORD dwFlags)
+{
+ hash_state md5_state;
+ unsigned char * pFileMd5;
+ unsigned char md5[MD5_DIGEST_SIZE];
+ TFileEntry * pFileEntry;
+ TMPQFile * hf;
+ BYTE Buffer[0x1000];
+ HANDLE hFile = NULL;
+ DWORD dwVerifyResult = 0;
+ DWORD dwTotalBytes = 0;
+ DWORD dwCrc32 = 0;
+
+ //
+ // Note: When the MPQ is patched, it will
+ // automatically check the patched version of the file
+ //
+
+ // Make sure the md5 is initialized
+ memset(md5, 0, sizeof(md5));
+
+ // If we have to verify raw data MD5, do it before file open
+ if(dwFlags & SFILE_VERIFY_RAW_MD5)
+ {
+ TMPQArchive * ha = (TMPQArchive *)hMpq;
+
+ // Parse the base MPQ and all patches
+ while(ha != NULL)
+ {
+ // Does the archive have support for raw MD5?
+ if(ha->pHeader->dwRawChunkSize != 0)
+ {
+ // The file has raw MD5 if the archive supports it
+ dwVerifyResult |= VERIFY_FILE_HAS_RAW_MD5;
+
+ // Find file entry for the file
+ pFileEntry = GetFileEntryLocale(ha, szFileName, lcFileLocale);
+ if(pFileEntry != NULL)
+ {
+ // If the file's raw MD5 doesn't match, don't bother with more checks
+ if(VerifyRawMpqData(ha, pFileEntry->ByteOffset, pFileEntry->dwCmpSize) != ERROR_SUCCESS)
+ return dwVerifyResult | VERIFY_FILE_RAW_MD5_ERROR;
+ }
+ }
+
+ // Move to the next patch
+ ha = ha->haPatch;
+ }
+ }
+
+ // Attempt to open the file
+ if(SFileOpenFileEx(hMpq, szFileName, SFILE_OPEN_FROM_MPQ, &hFile))
+ {
+ // Get the file size
+ hf = (TMPQFile *)hFile;
+ pFileEntry = hf->pFileEntry;
+ dwTotalBytes = SFileGetFileSize(hFile, NULL);
+
+ // Initialize the CRC32 and MD5 contexts
+ md5_init(&md5_state);
+ dwCrc32 = crc32(0, Z_NULL, 0);
+
+ // Also turn on sector checksum verification
+ if(dwFlags & SFILE_VERIFY_SECTOR_CRC)
+ hf->bCheckSectorCRCs = true;
+
+ // Go through entire file and update both CRC32 and MD5
+ for(;;)
+ {
+ DWORD dwBytesRead = 0;
+
+ // Read data from file
+ SFileReadFile(hFile, Buffer, sizeof(Buffer), &dwBytesRead, NULL);
+ if(dwBytesRead == 0)
+ {
+ if(GetLastError() == ERROR_CHECKSUM_ERROR)
+ dwVerifyResult |= VERIFY_FILE_SECTOR_CRC_ERROR;
+ break;
+ }
+
+ // Update CRC32 value
+ if(dwFlags & SFILE_VERIFY_FILE_CRC)
+ dwCrc32 = crc32(dwCrc32, Buffer, dwBytesRead);
+
+ // Update MD5 value
+ if(dwFlags & SFILE_VERIFY_FILE_MD5)
+ md5_process(&md5_state, Buffer, dwBytesRead);
+
+ // Decrement the total size
+ dwTotalBytes -= dwBytesRead;
+ }
+
+ // If the file has sector checksums, indicate it in the flags
+ if(dwFlags & SFILE_VERIFY_SECTOR_CRC)
+ {
+ if((hf->pFileEntry->dwFlags & MPQ_FILE_SECTOR_CRC) && hf->SectorChksums != NULL && hf->SectorChksums[0] != 0)
+ dwVerifyResult |= VERIFY_FILE_HAS_SECTOR_CRC;
+ }
+
+ // Check if the entire file has been read
+ // No point in checking CRC32 and MD5 if not
+ // Skip checksum checks if the file has patches
+ if(dwTotalBytes == 0)
+ {
+ // Check CRC32 and MD5 only if there is no patches
+ if(hf->hfPatch == NULL)
+ {
+ // Check if the CRC32 matches.
+ if(dwFlags & SFILE_VERIFY_FILE_CRC)
+ {
+ // Only check the CRC32 if it is valid
+ if(pFileEntry->dwCrc32 != 0)
+ {
+ dwVerifyResult |= VERIFY_FILE_HAS_CHECKSUM;
+ if(dwCrc32 != pFileEntry->dwCrc32)
+ dwVerifyResult |= VERIFY_FILE_CHECKSUM_ERROR;
+ }
+ }
+
+ // Check if MD5 matches
+ if(dwFlags & SFILE_VERIFY_FILE_MD5)
+ {
+ // Patch files have their MD5 saved in the patch info
+ pFileMd5 = (hf->pPatchInfo != NULL) ? hf->pPatchInfo->md5 : pFileEntry->md5;
+ md5_done(&md5_state, md5);
+
+ // Only check the MD5 if it is valid
+ if(IsValidMD5(pFileMd5))
+ {
+ dwVerifyResult |= VERIFY_FILE_HAS_MD5;
+ if(memcmp(md5, pFileMd5, MD5_DIGEST_SIZE))
+ dwVerifyResult |= VERIFY_FILE_MD5_ERROR;
+ }
+ }
+ }
+ else
+ {
+ // Patched files are MD5-checked automatically
+ dwVerifyResult |= VERIFY_FILE_HAS_MD5;
+ }
+ }
+ else
+ {
+ dwVerifyResult |= VERIFY_READ_ERROR;
+ }
+
+ SFileCloseFile(hFile);
+ }
+ else
+ {
+ // Remember that the file couldn't be open
+ dwVerifyResult |= VERIFY_OPEN_ERROR;
+ }
+
+ // If the caller required CRC32 and/or MD5, give it to him
+ if(pdwCrc32 != NULL)
+ *pdwCrc32 = dwCrc32;
+ if(pMD5 != NULL)
+ memcpy(pMD5, md5, MD5_DIGEST_SIZE);
+
+ return dwVerifyResult;
+}
+
+// Used in SFileGetFileInfo
+bool QueryMpqSignatureInfo(
+ TMPQArchive * ha,
+ PMPQ_SIGNATURE_INFO pSI)
+{
+ TFileEntry * pFileEntry;
+ ULONGLONG ExtraBytes;
+ DWORD dwFileSize;
+
+ // Make sure it's all zeroed
+ memset(pSI, 0, sizeof(MPQ_SIGNATURE_INFO));
+
+ // Calculate the range of the MPQ
+ CalculateArchiveRange(ha, pSI);
+
+ // If there is "(signature)" file in the MPQ, it has a weak signature
+ pFileEntry = GetFileEntryLocale(ha, SIGNATURE_NAME, LANG_NEUTRAL);
+ if(pFileEntry != NULL)
+ {
+ // Calculate the begin and end of the signature file itself
+ pSI->BeginExclude = ha->MpqPos + pFileEntry->ByteOffset;
+ pSI->EndExclude = pSI->BeginExclude + pFileEntry->dwCmpSize;
+ dwFileSize = (DWORD)(pSI->EndExclude - pSI->BeginExclude);
+
+ // Does the signature have proper size?
+ if(dwFileSize == MPQ_SIGNATURE_FILE_SIZE)
+ {
+ // Read the weak signature
+ if(!FileStream_Read(ha->pStream, &pSI->BeginExclude, pSI->Signature, dwFileSize))
+ return false;
+
+ pSI->SignatureTypes |= SIGNATURE_TYPE_WEAK;
+ pSI->cbSignatureSize = dwFileSize;
+ return true;
+ }
+ }
+
+ // If there is extra bytes beyond the end of the archive,
+ // it's the strong signature
+ ExtraBytes = pSI->EndOfFile - pSI->EndMpqData;
+ if(ExtraBytes >= (MPQ_STRONG_SIGNATURE_SIZE + 4))
+ {
+ // Read the strong signature
+ if(!FileStream_Read(ha->pStream, &pSI->EndMpqData, pSI->Signature, (MPQ_STRONG_SIGNATURE_SIZE + 4)))
+ return false;
+
+ // Check the signature header "NGIS"
+ if(pSI->Signature[0] != 'N' || pSI->Signature[1] != 'G' || pSI->Signature[2] != 'I' || pSI->Signature[3] != 'S')
+ return false;
+
+ pSI->SignatureTypes |= SIGNATURE_TYPE_STRONG;
+ return true;
+ }
+
+ // Succeeded, but no known signature found
+ return true;
+}
+
+//-----------------------------------------------------------------------------
+// Support for weak signature
+
+int SSignFileCreate(TMPQArchive * ha)
+{
+ TMPQFile * hf = NULL;
+ BYTE EmptySignature[MPQ_SIGNATURE_FILE_SIZE];
+ int nError = ERROR_SUCCESS;
+
+ // Only save the signature if we should do so
+ if(ha->dwFileFlags3 != 0)
+ {
+ // The (signature) file must be non-encrypted and non-compressed
+ assert(ha->dwFlags & MPQ_FLAG_SIGNATURE_NEW);
+ assert(ha->dwFileFlags3 == MPQ_FILE_EXISTS);
+ assert(ha->dwReservedFiles > 0);
+
+ // Create the (signature) file file in the MPQ
+ // Note that the file must not be compressed or encrypted
+ nError = SFileAddFile_Init(ha, SIGNATURE_NAME,
+ 0,
+ sizeof(EmptySignature),
+ LANG_NEUTRAL,
+ ha->dwFileFlags3 | MPQ_FILE_REPLACEEXISTING,
+ &hf);
+
+ // Write the empty signature file to the archive
+ if(nError == ERROR_SUCCESS)
+ {
+ // Write the empty zeroed file to the MPQ
+ memset(EmptySignature, 0, sizeof(EmptySignature));
+ nError = SFileAddFile_Write(hf, EmptySignature, (DWORD)sizeof(EmptySignature), 0);
+ SFileAddFile_Finish(hf);
+
+ // Clear the invalid mark
+ ha->dwFlags &= ~(MPQ_FLAG_SIGNATURE_NEW | MPQ_FLAG_SIGNATURE_NONE);
+ ha->dwReservedFiles--;
+ }
+ }
+
+ return nError;
+}
+
+int SSignFileFinish(TMPQArchive * ha)
+{
+ MPQ_SIGNATURE_INFO si;
+ unsigned long signature_len = MPQ_WEAK_SIGNATURE_SIZE;
+ BYTE WeakSignature[MPQ_SIGNATURE_FILE_SIZE];
+ BYTE Md5Digest[MD5_DIGEST_SIZE];
+ rsa_key key;
+ int hash_idx = find_hash("md5");
+
+ // Sanity checks
+ assert((ha->dwFlags & MPQ_FLAG_CHANGED) == 0);
+ assert(ha->dwFileFlags3 == MPQ_FILE_EXISTS);
+
+ // Query the weak signature info
+ memset(&si, 0, sizeof(MPQ_SIGNATURE_INFO));
+ if(!QueryMpqSignatureInfo(ha, &si))
+ return ERROR_FILE_CORRUPT;
+
+ // There must be exactly one signature
+ if(si.SignatureTypes != SIGNATURE_TYPE_WEAK)
+ return ERROR_FILE_CORRUPT;
+
+ // Calculate MD5 of the entire archive
+ if(!CalculateMpqHashMd5(ha, &si, Md5Digest))
+ return ERROR_VERIFY_FAILED;
+
+ // Decode the private key
+ if(!decode_base64_key(szBlizzardWeakPrivateKey, &key))
+ return ERROR_VERIFY_FAILED;
+
+ // Sign the hash
+ memset(WeakSignature, 0, sizeof(WeakSignature));
+ rsa_sign_hash_ex(Md5Digest, sizeof(Md5Digest), WeakSignature + 8, &signature_len, LTC_LTC_PKCS_1_V1_5, 0, 0, hash_idx, 0, &key);
+ memrev(WeakSignature + 8, MPQ_WEAK_SIGNATURE_SIZE);
+ rsa_free(&key);
+
+ // Write the signature to the MPQ. Don't use SFile* functions, but write the hash directly
+ if(!FileStream_Write(ha->pStream, &si.BeginExclude, WeakSignature, MPQ_SIGNATURE_FILE_SIZE))
+ return GetLastError();
+
+ return ERROR_SUCCESS;
+}
+
+//-----------------------------------------------------------------------------
+// Public (exported) functions
+
+bool WINAPI SFileGetFileChecksums(HANDLE hMpq, const char * szFileName, LPDWORD pdwCrc32, char * pMD5)
+{
+ DWORD dwVerifyResult;
+ DWORD dwVerifyFlags = 0;
+
+ if(pdwCrc32 != NULL)
+ dwVerifyFlags |= SFILE_VERIFY_FILE_CRC;
+ if(pMD5 != NULL)
+ dwVerifyFlags |= SFILE_VERIFY_FILE_MD5;
+
+ dwVerifyResult = VerifyFile(hMpq,
+ szFileName,
+ pdwCrc32,
+ pMD5,
+ dwVerifyFlags);
+
+ // If verification failed, return zero
+ if(dwVerifyResult & VERIFY_FILE_ERROR_MASK)
+ {
+ SetLastError(ERROR_FILE_CORRUPT);
+ return false;
+ }
+
+ return true;
+}
+
+
+DWORD WINAPI SFileVerifyFile(HANDLE hMpq, const char * szFileName, DWORD dwFlags)
+{
+ return VerifyFile(hMpq,
+ szFileName,
+ NULL,
+ NULL,
+ dwFlags);
+}
+
+// Verifies raw data of the archive Only works for MPQs version 4 or newer
+int WINAPI SFileVerifyRawData(HANDLE hMpq, DWORD dwWhatToVerify, const char * szFileName)
+{
+ TMPQArchive * ha = (TMPQArchive *)hMpq;
+ TFileEntry * pFileEntry;
+ TMPQHeader * pHeader;
+
+ // Verify input parameters
+ if(!IsValidMpqHandle(hMpq))
+ return ERROR_INVALID_PARAMETER;
+ pHeader = ha->pHeader;
+
+ // If the archive doesn't have raw data MD5, report it as OK
+ if(pHeader->dwRawChunkSize == 0)
+ return ERROR_SUCCESS;
+
+ // If we have to verify MPQ header, do it
+ switch(dwWhatToVerify)
+ {
+ case SFILE_VERIFY_MPQ_HEADER:
+
+ // Only if the header is of version 4 or newer
+ if(pHeader->dwHeaderSize >= (MPQ_HEADER_SIZE_V4 - MD5_DIGEST_SIZE))
+ return VerifyRawMpqData(ha, 0, MPQ_HEADER_SIZE_V4 - MD5_DIGEST_SIZE);
+ return ERROR_SUCCESS;
+
+ case SFILE_VERIFY_HET_TABLE:
+
+ // Only if we have HET table
+ if(pHeader->HetTablePos64 && pHeader->HetTableSize64)
+ return VerifyRawMpqData(ha, pHeader->HetTablePos64, (DWORD)pHeader->HetTableSize64);
+ return ERROR_SUCCESS;
+
+ case SFILE_VERIFY_BET_TABLE:
+
+ // Only if we have BET table
+ if(pHeader->BetTablePos64 && pHeader->BetTableSize64)
+ return VerifyRawMpqData(ha, pHeader->BetTablePos64, (DWORD)pHeader->BetTableSize64);
+ return ERROR_SUCCESS;
+
+ case SFILE_VERIFY_HASH_TABLE:
+
+ // Hash table is not protected by MD5
+ return ERROR_SUCCESS;
+
+ case SFILE_VERIFY_BLOCK_TABLE:
+
+ // Block table is not protected by MD5
+ return ERROR_SUCCESS;
+
+ case SFILE_VERIFY_HIBLOCK_TABLE:
+
+ // It is unknown if the hi-block table is protected my MD5 or not.
+ return ERROR_SUCCESS;
+
+ case SFILE_VERIFY_FILE:
+
+ // Verify parameters
+ if(szFileName == NULL || *szFileName == 0)
+ return ERROR_INVALID_PARAMETER;
+
+ // Get the offset of a file
+ pFileEntry = GetFileEntryLocale(ha, szFileName, lcFileLocale);
+ if(pFileEntry == NULL)
+ return ERROR_FILE_NOT_FOUND;
+
+ return VerifyRawMpqData(ha, pFileEntry->ByteOffset, pFileEntry->dwCmpSize);
+ }
+
+ return ERROR_INVALID_PARAMETER;
+}
+
+
+// Verifies the archive against the signature
+DWORD WINAPI SFileVerifyArchive(HANDLE hMpq)
+{
+ MPQ_SIGNATURE_INFO si;
+ TMPQArchive * ha = (TMPQArchive *)hMpq;
+
+ // Verify input parameters
+ if(!IsValidMpqHandle(hMpq))
+ return ERROR_VERIFY_FAILED;
+
+ // If the archive was modified, we need to flush it
+ if(ha->dwFlags & MPQ_FLAG_CHANGED)
+ SFileFlushArchive(hMpq);
+
+ // Get the MPQ signature and signature type
+ memset(&si, 0, sizeof(MPQ_SIGNATURE_INFO));
+ if(!QueryMpqSignatureInfo(ha, &si))
+ return ERROR_VERIFY_FAILED;
+
+ // If there is no signature
+ if(si.SignatureTypes == 0)
+ return ERROR_NO_SIGNATURE;
+
+ // We haven't seen a MPQ with both signatures
+ assert(si.SignatureTypes == SIGNATURE_TYPE_WEAK || si.SignatureTypes == SIGNATURE_TYPE_STRONG);
+
+ // Verify the strong signature, if present
+ if(si.SignatureTypes & SIGNATURE_TYPE_STRONG)
+ return VerifyStrongSignature(ha, &si);
+
+ // Verify the weak signature, if present
+ if(si.SignatureTypes & SIGNATURE_TYPE_WEAK)
+ return VerifyWeakSignature(ha, &si);
+
+ return ERROR_NO_SIGNATURE;
+}
+
+// Verifies the archive against the signature
+bool WINAPI SFileSignArchive(HANDLE hMpq, DWORD dwSignatureType)
+{
+ TMPQArchive * ha;
+
+ // Verify the archive handle
+ ha = IsValidMpqHandle(hMpq);
+ if(ha == NULL)
+ {
+ SetLastError(ERROR_INVALID_PARAMETER);
+ return false;
+ }
+
+ // We only support weak signature, and only for MPQs version 1.0
+ if(dwSignatureType != SIGNATURE_TYPE_WEAK)
+ {
+ SetLastError(ERROR_INVALID_PARAMETER);
+ return false;
+ }
+
+ // The archive must not be malformed and must not be read-only
+ if(ha->dwFlags & (MPQ_FLAG_READ_ONLY | MPQ_FLAG_MALFORMED))
+ {
+ SetLastError(ERROR_ACCESS_DENIED);
+ return false;
+ }
+
+ // If the signature is not there yet
+ if(ha->dwFileFlags3 == 0)
+ {
+ // Turn the signature on. The signature will
+ // be applied when the archive is closed
+ ha->dwFlags |= MPQ_FLAG_SIGNATURE_NEW | MPQ_FLAG_CHANGED;
+ ha->dwFileFlags3 = MPQ_FILE_EXISTS;
+ ha->dwReservedFiles++;
+ }
+
+ return true;
+}
+