You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

170 lines
4.5 KiB

#include <algorithm>
#include <cstddef>
#include <cstdint>
#include <cstring>
#include "appfat.h"
#include "sha.h"
#include "utils/endian.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