Revert "Add experimental stream archive support"
Some checks are pending
ci / headless-e2e (push) Waiting to run
runtime-self-hosted / runtime-ci (push) Waiting to run

This reverts commit ae3c3cb33c.
This commit is contained in:
server
2026-04-15 19:06:59 +02:00
parent ae3c3cb33c
commit e9bc8c7e15
4 changed files with 39 additions and 191 deletions

View File

@@ -15,6 +15,7 @@ namespace
{
constexpr char kArchiveMagic[kMagicSize] = {'M', '2', 'P', 'A', 'C', 'K', '2', '\0'};
constexpr std::uint32_t kArchiveVersion = 1;
constexpr std::size_t kCompressionMinSavingsBytes = 64;
constexpr std::size_t kCompressionMinSavingsPercent = 5;
@@ -45,35 +46,6 @@ Compression select_compression_mode(
return Compression::Zstd;
}
bool supports_archive_version(std::uint32_t version)
{
return version == kArchiveVersionAead || version == kArchiveVersionStream;
}
std::vector<std::uint8_t> decrypt_payload_for_entry(
std::uint32_t version,
const std::vector<std::uint8_t>& ciphertext,
const ManifestEntry& entry,
const std::array<std::uint8_t, kAeadKeySize>& master_key)
{
if (version == kArchiveVersionAead)
{
return decrypt_payload(ciphertext, master_key, entry.nonce, entry.path);
}
if (version == kArchiveVersionStream)
{
auto payload = decrypt_payload_stream(ciphertext, master_key, entry.nonce);
if (hash_bytes(payload) != entry.payload_hash)
{
fail("Payload hash mismatch: " + entry.path);
}
return payload;
}
fail("Unsupported archive version");
}
} // namespace
std::string normalize_path(const std::filesystem::path& root, const std::filesystem::path& file)
@@ -102,7 +74,7 @@ std::vector<std::filesystem::path> collect_files(const std::filesystem::path& ro
return files;
}
std::vector<std::uint8_t> serialize_manifest(const std::vector<ManifestEntry>& entries, std::uint32_t version)
std::vector<std::uint8_t> serialize_manifest(const std::vector<ManifestEntry>& entries)
{
std::vector<std::uint8_t> bytes;
const ManifestFixedHeader fixed {
@@ -118,45 +90,23 @@ std::vector<std::uint8_t> serialize_manifest(const std::vector<ManifestEntry>& e
fail("Path too long for manifest entry: " + entry.path);
}
if (version == kArchiveVersionAead)
{
ManifestEntryFixed pod {};
pod.path_size = static_cast<std::uint16_t>(entry.path.size());
pod.compression = static_cast<std::uint8_t>(entry.compression);
pod.flags = 0;
pod.data_offset = entry.data_offset;
pod.original_size = entry.original_size;
pod.stored_size = entry.stored_size;
std::memcpy(pod.nonce, entry.nonce.data(), entry.nonce.size());
std::memcpy(pod.plaintext_hash, entry.plaintext_hash.data(), entry.plaintext_hash.size());
append_pod(bytes, pod);
}
else if (version == kArchiveVersionStream)
{
ManifestEntryFixedV2 pod {};
pod.path_size = static_cast<std::uint16_t>(entry.path.size());
pod.compression = static_cast<std::uint8_t>(entry.compression);
pod.flags = 0;
pod.data_offset = entry.data_offset;
pod.original_size = entry.original_size;
pod.stored_size = entry.stored_size;
std::memcpy(pod.nonce, entry.nonce.data(), entry.nonce.size());
std::memcpy(pod.payload_hash, entry.payload_hash.data(), entry.payload_hash.size());
std::memcpy(pod.plaintext_hash, entry.plaintext_hash.data(), entry.plaintext_hash.size());
append_pod(bytes, pod);
}
else
{
fail("Unsupported archive version");
}
ManifestEntryFixed pod {};
pod.path_size = static_cast<std::uint16_t>(entry.path.size());
pod.compression = static_cast<std::uint8_t>(entry.compression);
pod.flags = 0;
pod.data_offset = entry.data_offset;
pod.original_size = entry.original_size;
pod.stored_size = entry.stored_size;
std::memcpy(pod.nonce, entry.nonce.data(), entry.nonce.size());
std::memcpy(pod.plaintext_hash, entry.plaintext_hash.data(), entry.plaintext_hash.size());
append_pod(bytes, pod);
bytes.insert(bytes.end(), entry.path.begin(), entry.path.end());
}
return bytes;
}
std::vector<ManifestEntry> parse_manifest(const std::vector<std::uint8_t>& bytes, std::uint32_t version)
std::vector<ManifestEntry> parse_manifest(const std::vector<std::uint8_t>& bytes)
{
std::size_t offset = 0;
const auto fixed = read_pod<ManifestFixedHeader>(bytes, offset);
@@ -166,44 +116,21 @@ std::vector<ManifestEntry> parse_manifest(const std::vector<std::uint8_t>& bytes
for (std::uint32_t i = 0; i < fixed.entry_count; ++i)
{
ManifestEntry entry;
std::uint16_t path_size = 0;
if (version == kArchiveVersionAead)
{
const auto pod = read_pod<ManifestEntryFixed>(bytes, offset);
path_size = pod.path_size;
entry.compression = static_cast<Compression>(pod.compression);
entry.data_offset = pod.data_offset;
entry.original_size = pod.original_size;
entry.stored_size = pod.stored_size;
std::memcpy(entry.nonce.data(), pod.nonce, entry.nonce.size());
std::memcpy(entry.plaintext_hash.data(), pod.plaintext_hash, entry.plaintext_hash.size());
}
else if (version == kArchiveVersionStream)
{
const auto pod = read_pod<ManifestEntryFixedV2>(bytes, offset);
path_size = pod.path_size;
entry.compression = static_cast<Compression>(pod.compression);
entry.data_offset = pod.data_offset;
entry.original_size = pod.original_size;
entry.stored_size = pod.stored_size;
std::memcpy(entry.nonce.data(), pod.nonce, entry.nonce.size());
std::memcpy(entry.payload_hash.data(), pod.payload_hash, entry.payload_hash.size());
std::memcpy(entry.plaintext_hash.data(), pod.plaintext_hash, entry.plaintext_hash.size());
}
else
{
fail("Unsupported archive version");
}
if (offset + path_size > bytes.size())
const auto pod = read_pod<ManifestEntryFixed>(bytes, offset);
if (offset + pod.path_size > bytes.size())
{
fail("Manifest path data exceeds buffer");
}
entry.path.assign(reinterpret_cast<const char*>(bytes.data() + offset), path_size);
offset += path_size;
ManifestEntry entry;
entry.path.assign(reinterpret_cast<const char*>(bytes.data() + offset), pod.path_size);
offset += pod.path_size;
entry.compression = static_cast<Compression>(pod.compression);
entry.data_offset = pod.data_offset;
entry.original_size = pod.original_size;
entry.stored_size = pod.stored_size;
std::memcpy(entry.nonce.data(), pod.nonce, entry.nonce.size());
std::memcpy(entry.plaintext_hash.data(), pod.plaintext_hash, entry.plaintext_hash.size());
entries.push_back(std::move(entry));
}
@@ -244,23 +171,10 @@ BuildResult build_archive(
entry.data_offset = payload_bytes.size();
entry.original_size = plain.size();
entry.nonce = random_nonce();
const auto& payload = entry.compression == Compression::Zstd ? compressed : plain;
entry.payload_hash = hash_bytes(payload);
entry.plaintext_hash = hash_bytes(plain);
std::vector<std::uint8_t> encrypted;
if (kCurrentArchiveVersion == kArchiveVersionAead)
{
encrypted = encrypt_payload(payload, require_master_key(keys), entry.nonce, entry.path);
}
else if (kCurrentArchiveVersion == kArchiveVersionStream)
{
encrypted = encrypt_payload_stream(payload, require_master_key(keys), entry.nonce);
}
else
{
fail("Unsupported archive version");
}
const auto& payload = entry.compression == Compression::Zstd ? compressed : plain;
const auto encrypted = encrypt_payload(payload, require_master_key(keys), entry.nonce, entry.path);
entry.stored_size = encrypted.size();
payload_bytes.insert(payload_bytes.end(), encrypted.begin(), encrypted.end());
@@ -271,13 +185,13 @@ BuildResult build_archive(
result.total_stored_bytes += encrypted.size();
}
const auto manifest_bytes = serialize_manifest(manifest_entries, kCurrentArchiveVersion);
const auto manifest_bytes = serialize_manifest(manifest_entries);
const auto manifest_hash = hash_bytes(manifest_bytes);
const auto signature = sign_detached(manifest_bytes, *keys.signing_secret_key);
ArchiveHeader header {};
std::memcpy(header.magic, kArchiveMagic, sizeof(kArchiveMagic));
header.version = kCurrentArchiveVersion;
header.version = kArchiveVersion;
header.flags = 0;
header.key_id = keys.key_id;
header.manifest_offset = sizeof(ArchiveHeader) + payload_bytes.size();
@@ -325,7 +239,7 @@ LoadedArchive load_archive(const std::filesystem::path& archive_path)
{
fail("Archive magic mismatch");
}
if (!supports_archive_version(archive.header.version))
if (archive.header.version != kArchiveVersion)
{
fail("Unsupported archive version");
}
@@ -338,7 +252,7 @@ LoadedArchive load_archive(const std::filesystem::path& archive_path)
archive.file_bytes.begin() + static_cast<std::ptrdiff_t>(archive.header.manifest_offset),
archive.file_bytes.begin() + static_cast<std::ptrdiff_t>(archive.header.manifest_offset + archive.header.manifest_size));
archive.entries = parse_manifest(archive.manifest_bytes, archive.header.version);
archive.entries = parse_manifest(archive.manifest_bytes);
return archive;
}
@@ -394,14 +308,11 @@ bool verify_archive(
for (const auto& entry : archive.entries)
{
const auto plain = extract_entry(archive, entry, keys.master_key);
if (archive.header.version == kArchiveVersionAead || archive.header.version == kArchiveVersionStream)
const auto hash = hash_bytes(plain);
if (hash != entry.plaintext_hash)
{
const auto hash = hash_bytes(plain);
if (hash != entry.plaintext_hash)
{
error = "Plaintext hash mismatch: " + entry.path;
return false;
}
error = "Plaintext hash mismatch: " + entry.path;
return false;
}
}
}
@@ -426,13 +337,13 @@ std::vector<std::uint8_t> extract_entry(
archive.file_bytes.begin() + static_cast<std::ptrdiff_t>(begin),
archive.file_bytes.begin() + static_cast<std::ptrdiff_t>(end));
const auto payload = decrypt_payload_for_entry(archive.header.version, ciphertext, entry, master_key);
const auto compressed = decrypt_payload(ciphertext, master_key, entry.nonce, entry.path);
switch (entry.compression)
{
case Compression::None:
return payload;
return compressed;
case Compression::Zstd:
return decompress_zstd(payload, static_cast<std::size_t>(entry.original_size));
return decompress_zstd(compressed, static_cast<std::size_t>(entry.original_size));
default:
fail("Unsupported compression mode");
}

View File

@@ -16,9 +16,6 @@ constexpr std::size_t kHashSize = 32;
constexpr std::size_t kSignatureSize = 64;
constexpr std::size_t kAeadKeySize = 32;
constexpr std::size_t kAeadNonceSize = 24;
constexpr std::uint32_t kArchiveVersionAead = 1;
constexpr std::uint32_t kArchiveVersionStream = 2;
constexpr std::uint32_t kCurrentArchiveVersion = kArchiveVersionAead;
enum class Compression : std::uint8_t
{
@@ -57,19 +54,6 @@ struct ManifestEntryFixed
std::uint8_t nonce[kAeadNonceSize];
std::uint8_t plaintext_hash[kHashSize];
};
struct ManifestEntryFixedV2
{
std::uint16_t path_size;
std::uint8_t compression;
std::uint8_t flags;
std::uint64_t data_offset;
std::uint64_t original_size;
std::uint64_t stored_size;
std::uint8_t nonce[kAeadNonceSize];
std::uint8_t payload_hash[kHashSize];
std::uint8_t plaintext_hash[kHashSize];
};
#pragma pack(pop)
struct ManifestEntry
@@ -80,7 +64,6 @@ struct ManifestEntry
std::uint64_t original_size = 0;
std::uint64_t stored_size = 0;
std::array<std::uint8_t, kAeadNonceSize> nonce {};
std::array<std::uint8_t, kHashSize> payload_hash {};
std::array<std::uint8_t, kHashSize> plaintext_hash {};
};
@@ -133,7 +116,7 @@ void extract_all(
const std::filesystem::path& output_dir,
const std::array<std::uint8_t, kAeadKeySize>& master_key);
std::vector<std::uint8_t> serialize_manifest(const std::vector<ManifestEntry>& entries, std::uint32_t version);
std::vector<ManifestEntry> parse_manifest(const std::vector<std::uint8_t>& bytes, std::uint32_t version);
std::vector<std::uint8_t> serialize_manifest(const std::vector<ManifestEntry>& entries);
std::vector<ManifestEntry> parse_manifest(const std::vector<std::uint8_t>& bytes);
} // namespace m2pack

View File

@@ -205,42 +205,6 @@ std::vector<std::uint8_t> decrypt_payload(
return plaintext;
}
std::vector<std::uint8_t> encrypt_payload_stream(
const std::vector<std::uint8_t>& plaintext,
const std::array<std::uint8_t, kAeadKeySize>& key,
const std::array<std::uint8_t, kAeadNonceSize>& nonce)
{
std::vector<std::uint8_t> ciphertext(plaintext.size());
if (!plaintext.empty())
{
crypto_stream_xchacha20_xor(
ciphertext.data(),
plaintext.data(),
plaintext.size(),
nonce.data(),
key.data());
}
return ciphertext;
}
std::vector<std::uint8_t> decrypt_payload_stream(
const std::vector<std::uint8_t>& ciphertext,
const std::array<std::uint8_t, kAeadKeySize>& key,
const std::array<std::uint8_t, kAeadNonceSize>& nonce)
{
std::vector<std::uint8_t> plaintext(ciphertext.size());
if (!ciphertext.empty())
{
crypto_stream_xchacha20_xor(
plaintext.data(),
ciphertext.data(),
ciphertext.size(),
nonce.data(),
key.data());
}
return plaintext;
}
std::vector<std::uint8_t> sign_detached(
const std::vector<std::uint8_t>& data,
const std::vector<std::uint8_t>& secret_key)

View File

@@ -38,16 +38,6 @@ std::vector<std::uint8_t> decrypt_payload(
const std::array<std::uint8_t, kAeadNonceSize>& nonce,
std::string_view associated_data);
std::vector<std::uint8_t> encrypt_payload_stream(
const std::vector<std::uint8_t>& plaintext,
const std::array<std::uint8_t, kAeadKeySize>& key,
const std::array<std::uint8_t, kAeadNonceSize>& nonce);
std::vector<std::uint8_t> decrypt_payload_stream(
const std::vector<std::uint8_t>& ciphertext,
const std::array<std::uint8_t, kAeadKeySize>& key,
const std::array<std::uint8_t, kAeadNonceSize>& nonce);
std::vector<std::uint8_t> sign_detached(
const std::vector<std::uint8_t>& data,
const std::vector<std::uint8_t>& secret_key);