diff --git a/src/PackLib/M2Pack.cpp b/src/PackLib/M2Pack.cpp index 9a8f5e1..a1814b7 100644 --- a/src/PackLib/M2Pack.cpp +++ b/src/PackLib/M2Pack.cpp @@ -4,7 +4,6 @@ #include #include - #include #include "EterBase/Debug.h" @@ -38,6 +37,44 @@ bool ReadPod(const uint8_t* bytes, std::size_t size, std::size_t& offset, T& out return true; } +bool IsSupportedM2PackVersion(uint32_t version) +{ + return version == M2PACK_VERSION_AEAD || version == M2PACK_VERSION_STREAM; +} + +bool VerifyM2PackHash( + std::string_view stage, + const uint8_t* input, + std::size_t inputSize, + const std::array& expected, + const char* path) +{ + std::array actual {}; + const auto hashStart = std::chrono::steady_clock::now(); + crypto_generichash( + actual.data(), + actual.size(), + input, + inputSize, + nullptr, + 0); + RecordPackProfileStage( + "m2p", + stage, + inputSize, + actual.size(), + static_cast(std::chrono::duration_cast( + std::chrono::steady_clock::now() - hashStart).count())); + + if (memcmp(actual.data(), expected.data(), actual.size()) != 0) + { + TraceError("CM2Pack: %s mismatch for '%s'", std::string(stage).c_str(), path); + return false; + } + + return true; +} + } bool CM2Pack::Load(const std::string& path) @@ -66,7 +103,7 @@ bool CM2Pack::Load(const std::string& path) return false; } - if (m_header.version != 1) + if (!IsSupportedM2PackVersion(m_header.version)) { TraceError("CM2Pack::Load: unsupported version %u in '%s'", m_header.version, path.c_str()); return false; @@ -170,26 +207,54 @@ bool CM2Pack::ValidateManifest() for (uint32_t i = 0; i < manifest_header.entry_count; ++i) { - TM2PackManifestEntryFixed fixed {}; - if (!ReadPod(m_manifest_bytes.data(), m_manifest_bytes.size(), offset, fixed)) - { - return false; - } - - if (offset + fixed.path_size > m_manifest_bytes.size()) - { - return false; - } - TM2PackEntry entry; - entry.path.assign(reinterpret_cast(m_manifest_bytes.data() + offset), fixed.path_size); - offset += fixed.path_size; - entry.compression = fixed.compression; - entry.data_offset = fixed.data_offset; - entry.original_size = fixed.original_size; - entry.stored_size = fixed.stored_size; - memcpy(entry.nonce.data(), fixed.nonce, entry.nonce.size()); - memcpy(entry.plaintext_hash.data(), fixed.plaintext_hash, entry.plaintext_hash.size()); + uint16_t pathSize = 0; + + if (m_header.version == M2PACK_VERSION_AEAD) + { + TM2PackManifestEntryFixed fixed {}; + if (!ReadPod(m_manifest_bytes.data(), m_manifest_bytes.size(), offset, fixed)) + { + return false; + } + + pathSize = fixed.path_size; + entry.compression = fixed.compression; + entry.data_offset = fixed.data_offset; + entry.original_size = fixed.original_size; + entry.stored_size = fixed.stored_size; + memcpy(entry.nonce.data(), fixed.nonce, entry.nonce.size()); + memcpy(entry.plaintext_hash.data(), fixed.plaintext_hash, entry.plaintext_hash.size()); + } + else if (m_header.version == M2PACK_VERSION_STREAM) + { + TM2PackManifestEntryFixedV2 fixed {}; + if (!ReadPod(m_manifest_bytes.data(), m_manifest_bytes.size(), offset, fixed)) + { + return false; + } + + pathSize = fixed.path_size; + entry.compression = fixed.compression; + entry.data_offset = fixed.data_offset; + entry.original_size = fixed.original_size; + entry.stored_size = fixed.stored_size; + memcpy(entry.nonce.data(), fixed.nonce, entry.nonce.size()); + memcpy(entry.payload_hash.data(), fixed.payload_hash, entry.payload_hash.size()); + memcpy(entry.plaintext_hash.data(), fixed.plaintext_hash, entry.plaintext_hash.size()); + } + else + { + return false; + } + + if (offset + pathSize > m_manifest_bytes.size()) + { + return false; + } + + entry.path.assign(reinterpret_cast(m_manifest_bytes.data() + offset), pathSize); + offset += pathSize; const uint64_t payload_begin = sizeof(TM2PackHeader); const uint64_t payload_end = m_header.manifest_offset; @@ -231,17 +296,66 @@ bool CM2Pack::DecryptEntryPayload(const TM2PackEntry& entry, std::vector(entry.path.data()), - entry.path.size(), - entry.nonce.data(), - GetM2PackActiveMasterKey().data()) != 0) + std::size_t written = entry.stored_size; + + if (m_header.version == M2PACK_VERSION_AEAD) + { + unsigned long long aeadWritten = 0; + if (crypto_aead_xchacha20poly1305_ietf_decrypt( + decrypted.data(), + &aeadWritten, + nullptr, + ciphertext, + entry.stored_size, + reinterpret_cast(entry.path.data()), + entry.path.size(), + entry.nonce.data(), + GetM2PackActiveMasterKey().data()) != 0) + { + if (pPool) + { + pPool->Release(std::move(decrypted)); + } + return false; + } + written = static_cast(aeadWritten); + RecordPackProfileStage( + "m2p", + "aead_decrypt", + entry.stored_size, + written, + static_cast(std::chrono::duration_cast( + std::chrono::steady_clock::now() - decryptStart).count())); + } + else if (m_header.version == M2PACK_VERSION_STREAM) + { + if (!decrypted.empty()) + { + crypto_stream_xchacha20_xor( + decrypted.data(), + ciphertext, + entry.stored_size, + entry.nonce.data(), + GetM2PackActiveMasterKey().data()); + } + RecordPackProfileStage( + "m2p", + "stream_decrypt", + entry.stored_size, + written, + static_cast(std::chrono::duration_cast( + std::chrono::steady_clock::now() - decryptStart).count())); + + if (!VerifyM2PackHash("payload_hash", decrypted.data(), decrypted.size(), entry.payload_hash, entry.path.c_str())) + { + if (pPool) + { + pPool->Release(std::move(decrypted)); + } + return false; + } + } + else { if (pPool) { @@ -249,15 +363,8 @@ bool CM2Pack::DecryptEntryPayload(const TM2PackEntry& entry, std::vector(std::chrono::duration_cast( - std::chrono::steady_clock::now() - decryptStart).count())); - decrypted.resize(static_cast(written)); + decrypted.resize(written); return true; } @@ -315,32 +422,12 @@ bool CM2Pack::GetFileWithPool(const TM2PackEntry& entry, std::vector& r return false; } - if (!ShouldVerifyM2PackPlaintextHash()) + if (ShouldVerifyM2PackPlaintextHash()) { - return true; - } - - std::array plain_hash {}; - const auto hashStart = std::chrono::steady_clock::now(); - crypto_generichash( - plain_hash.data(), - plain_hash.size(), - result.data(), - result.size(), - nullptr, - 0); - RecordPackProfileStage( - "m2p", - "plaintext_hash", - result.size(), - plain_hash.size(), - static_cast(std::chrono::duration_cast( - std::chrono::steady_clock::now() - hashStart).count())); - - if (memcmp(plain_hash.data(), entry.plaintext_hash.data(), plain_hash.size()) != 0) - { - TraceError("CM2Pack::GetFileWithPool: plaintext hash mismatch for '%s'", entry.path.c_str()); - return false; + if (!VerifyM2PackHash("plaintext_hash", result.data(), result.size(), entry.plaintext_hash, entry.path.c_str())) + { + return false; + } } return true; diff --git a/src/PackLib/M2Pack.h b/src/PackLib/M2Pack.h index eb2ae8f..68e6d2c 100644 --- a/src/PackLib/M2Pack.h +++ b/src/PackLib/M2Pack.h @@ -19,6 +19,8 @@ constexpr std::size_t M2PACK_SIGNATURE_SIZE = 64; constexpr std::size_t M2PACK_KEY_SIZE = crypto_aead_xchacha20poly1305_ietf_KEYBYTES; constexpr std::size_t M2PACK_NONCE_SIZE = crypto_aead_xchacha20poly1305_ietf_NPUBBYTES; constexpr std::size_t M2PACK_PUBLIC_KEY_SIZE = crypto_sign_PUBLICKEYBYTES; +constexpr uint32_t M2PACK_VERSION_AEAD = 1; +constexpr uint32_t M2PACK_VERSION_STREAM = 2; #pragma pack(push, 1) struct TM2PackHeader @@ -51,6 +53,19 @@ struct TM2PackManifestEntryFixed uint8_t nonce[M2PACK_NONCE_SIZE]; uint8_t plaintext_hash[M2PACK_HASH_SIZE]; }; + +struct TM2PackManifestEntryFixedV2 +{ + uint16_t path_size; + uint8_t compression; + uint8_t flags; + uint64_t data_offset; + uint64_t original_size; + uint64_t stored_size; + uint8_t nonce[M2PACK_NONCE_SIZE]; + uint8_t payload_hash[M2PACK_HASH_SIZE]; + uint8_t plaintext_hash[M2PACK_HASH_SIZE]; +}; #pragma pack(pop) struct TM2PackEntry @@ -61,6 +76,7 @@ struct TM2PackEntry uint64_t original_size = 0; uint64_t stored_size = 0; std::array nonce {}; + std::array payload_hash {}; std::array plaintext_hash {}; };