|
|
|
|
#include <algorithm>
|
|
|
|
|
#include <cstddef>
|
|
|
|
|
#include <cstdint>
|
|
|
|
|
#include <cstring>
|
|
|
|
|
|
|
|
|
|
#include "appfat.h"
|
|
|
|
|
#include "sha.h"
|
|
|
|
|
#include "utils/endian_read.hpp"
|
|
|
|
|
#include "utils/log.hpp"
|
|
|
|
|
|
|
|
|
|
namespace devilution {
|
|
|
|
|
namespace {
|
|
|
|
|
|
|
|
|
|
struct CodecSignature {
|
|
|
|
|
uint32_t checksum;
|
|
|
|
|
uint8_t error;
|
|
|
|
|
uint8_t lastChunkSize;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
constexpr size_t BlockSizeBytes = BlockSize * sizeof(uint32_t);
|
|
|
|
|
constexpr size_t SignatureSize = 8;
|
|
|
|
|
|
|
|
|
|
SHA1Context CodecInitKey(const char *pszPassword)
|
|
|
|
|
{
|
|
|
|
|
uint32_t pw[BlockSize]; // Repeat password until 64 char long
|
|
|
|
|
std::size_t j = 0;
|
|
|
|
|
for (uint32_t &value : pw) {
|
|
|
|
|
if (pszPassword[j] == '\0')
|
|
|
|
|
j = 0;
|
|
|
|
|
value = LoadLE32(&pszPassword[j]);
|
|
|
|
|
j += sizeof(uint32_t);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
uint32_t digest[SHA1HashSize];
|
|
|
|
|
{
|
|
|
|
|
SHA1Context context;
|
|
|
|
|
SHA1Calculate(context, pw);
|
|
|
|
|
SHA1Result(context, digest);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
uint32_t key[BlockSize] {
|
|
|
|
|
2908958655, 4146550480, 658981742, 1113311088, 3927878744, 679301322, 1760465731, 3305370375,
|
|
|
|
|
2269115995, 3928541685, 580724401, 2607446661, 2233092279, 2416822349, 4106933702, 3046442503
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
for (unsigned i = 0; i < BlockSize; ++i) {
|
|
|
|
|
key[i] ^= digest[(i + 3) % SHA1HashSize];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
SHA1Context context;
|
|
|
|
|
SHA1Calculate(context, key);
|
|
|
|
|
return context;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
CodecSignature GetCodecSignature(std::byte *src)
|
|
|
|
|
{
|
|
|
|
|
CodecSignature result;
|
|
|
|
|
result.checksum = LoadLE32(src);
|
|
|
|
|
src += 4;
|
|
|
|
|
result.error = static_cast<uint8_t>(*src++);
|
|
|
|
|
result.lastChunkSize = static_cast<uint8_t>(*src);
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void SetCodecSignature(std::byte *dst, CodecSignature sig)
|
|
|
|
|
{
|
|
|
|
|
*dst++ = static_cast<std::byte>(sig.checksum);
|
|
|
|
|
*dst++ = static_cast<std::byte>(sig.checksum >> 8);
|
|
|
|
|
*dst++ = static_cast<std::byte>(sig.checksum >> 16);
|
|
|
|
|
*dst++ = static_cast<std::byte>(sig.checksum >> 24);
|
|
|
|
|
*dst++ = static_cast<std::byte>(sig.error);
|
|
|
|
|
*dst++ = static_cast<std::byte>(sig.lastChunkSize);
|
|
|
|
|
*dst++ = static_cast<std::byte>(0);
|
|
|
|
|
*dst++ = static_cast<std::byte>(0);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void ByteSwapBlock(uint32_t *data)
|
|
|
|
|
{
|
|
|
|
|
for (size_t i = 0; i < BlockSize; ++i)
|
|
|
|
|
data[i] = SDL_SwapLE32(data[i]);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void XorBlock(const uint32_t *shaResult, uint32_t *out)
|
|
|
|
|
{
|
|
|
|
|
for (unsigned i = 0; i < BlockSize; ++i)
|
|
|
|
|
out[i] ^= shaResult[i % SHA1HashSize];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
} // namespace
|
|
|
|
|
|
|
|
|
|
std::size_t codec_decode(std::byte *pbSrcDst, std::size_t size, const char *pszPassword)
|
|
|
|
|
{
|
|
|
|
|
uint32_t buf[BlockSize];
|
|
|
|
|
uint32_t dst[SHA1HashSize];
|
|
|
|
|
|
|
|
|
|
SHA1Context context = CodecInitKey(pszPassword);
|
|
|
|
|
if (size <= SignatureSize)
|
|
|
|
|
return 0;
|
|
|
|
|
size -= SignatureSize;
|
|
|
|
|
if (size % BlockSize != 0)
|
|
|
|
|
return 0;
|
|
|
|
|
for (size_t i = 0; i < size; pbSrcDst += BlockSizeBytes, i += BlockSizeBytes) {
|
|
|
|
|
memcpy(buf, pbSrcDst, BlockSizeBytes);
|
|
|
|
|
ByteSwapBlock(buf);
|
|
|
|
|
SHA1Result(context, dst);
|
|
|
|
|
XorBlock(dst, buf);
|
|
|
|
|
SHA1Calculate(context, buf);
|
|
|
|
|
ByteSwapBlock(buf);
|
|
|
|
|
memcpy(pbSrcDst, buf, BlockSizeBytes);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
memset(buf, 0, sizeof(buf));
|
|
|
|
|
const CodecSignature sig = GetCodecSignature(pbSrcDst);
|
|
|
|
|
if (sig.error > 0) {
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
SHA1Result(context, dst);
|
|
|
|
|
if (sig.checksum != dst[0]) {
|
|
|
|
|
LogError("Checksum mismatch signature={} vs calculated={}", sig.checksum, dst[0]);
|
|
|
|
|
memset(dst, 0, sizeof(dst));
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
size += sig.lastChunkSize - BlockSizeBytes;
|
|
|
|
|
return size;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
std::size_t codec_get_encoded_len(std::size_t dwSrcBytes)
|
|
|
|
|
{
|
|
|
|
|
if (dwSrcBytes % BlockSizeBytes != 0)
|
|
|
|
|
dwSrcBytes += BlockSizeBytes - (dwSrcBytes % BlockSizeBytes);
|
|
|
|
|
return dwSrcBytes + SignatureSize;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void codec_encode(std::byte *pbSrcDst, std::size_t size, std::size_t size64, const char *pszPassword)
|
|
|
|
|
{
|
|
|
|
|
uint32_t buf[BlockSize];
|
|
|
|
|
uint32_t tmp[SHA1HashSize];
|
|
|
|
|
uint32_t dst[SHA1HashSize];
|
|
|
|
|
|
|
|
|
|
if (size64 != codec_get_encoded_len(size))
|
|
|
|
|
app_fatal("Invalid encode parameters");
|
|
|
|
|
SHA1Context context = CodecInitKey(pszPassword);
|
|
|
|
|
|
|
|
|
|
size_t lastChunk = 0;
|
|
|
|
|
while (size != 0) {
|
|
|
|
|
const size_t chunk = std::min(size, BlockSizeBytes);
|
|
|
|
|
memset(buf, 0, sizeof(buf));
|
|
|
|
|
memcpy(buf, pbSrcDst, chunk);
|
|
|
|
|
ByteSwapBlock(buf);
|
|
|
|
|
SHA1Result(context, dst);
|
|
|
|
|
SHA1Calculate(context, buf);
|
|
|
|
|
XorBlock(dst, buf);
|
|
|
|
|
ByteSwapBlock(buf);
|
|
|
|
|
memcpy(pbSrcDst, buf, BlockSizeBytes);
|
|
|
|
|
pbSrcDst += BlockSizeBytes;
|
|
|
|
|
lastChunk = chunk;
|
|
|
|
|
size -= chunk;
|
|
|
|
|
}
|
|
|
|
|
memset(buf, 0, sizeof(buf));
|
|
|
|
|
SHA1Result(context, tmp);
|
|
|
|
|
SetCodecSignature(pbSrcDst, CodecSignature { /*.checksum=*/*reinterpret_cast<uint32_t *>(tmp),
|
|
|
|
|
/*.error=*/0,
|
|
|
|
|
// lastChunk is at most 64 so will always fit in an 8 bit var
|
|
|
|
|
/*.lastChunkSize=*/static_cast<uint8_t>(lastChunk) });
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
} // namespace devilution
|