mirror of
https://github.com/HarbourMasters/Shipwright.git
synced 2024-11-26 11:22:17 -05:00
b3e299dbde
Some modifications to handle backslashes and forward slashes, along with some optimizations to speed up OTR generation.
1055 lines
35 KiB
C++
1055 lines
35 KiB
C++
/*****************************************************************************/
|
|
/* 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 DWORD 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;
|
|
DWORD dwErrCode = 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)
|
|
dwErrCode = ERROR_NOT_ENOUGH_MEMORY;
|
|
|
|
// Calculate MD5 of each data chunk
|
|
if(dwErrCode == 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))
|
|
{
|
|
dwErrCode = 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(dwErrCode == ERROR_SUCCESS)
|
|
{
|
|
// Read the array of MD5
|
|
if(!FileStream_Read(ha->pStream, &DataOffset, pbMD5Array2, dwMD5Size))
|
|
dwErrCode = GetLastError();
|
|
}
|
|
|
|
// Compare the array of MD5
|
|
if(dwErrCode == ERROR_SUCCESS)
|
|
{
|
|
// Compare the MD5
|
|
if(memcmp(pbMD5Array1, pbMD5Array2, dwMD5Size))
|
|
dwErrCode = 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 dwErrCode;
|
|
}
|
|
|
|
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, g_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 true; //Not a valid signature, but another filetype could've been appended so not always an error.
|
|
|
|
pSI->SignatureTypes |= SIGNATURE_TYPE_STRONG;
|
|
return true;
|
|
}
|
|
|
|
// Succeeded, but no known signature found
|
|
return true;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Support for weak signature
|
|
|
|
DWORD SSignFileCreate(TMPQArchive * ha)
|
|
{
|
|
TMPQFile * hf = NULL;
|
|
BYTE EmptySignature[MPQ_SIGNATURE_FILE_SIZE];
|
|
DWORD dwErrCode = 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
|
|
dwErrCode = 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(dwErrCode == ERROR_SUCCESS)
|
|
{
|
|
// Write the empty zeroed file to the MPQ
|
|
memset(EmptySignature, 0, sizeof(EmptySignature));
|
|
dwErrCode = 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 dwErrCode;
|
|
}
|
|
|
|
DWORD 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
|
|
DWORD 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, g_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;
|
|
}
|
|
|