9.5 KiB
phase, plan, type, wave, depends_on, files_modified, autonomous, requirements, must_haves
| phase | plan | type | wave | depends_on | files_modified | autonomous | requirements | must_haves | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 06-obfuscation-hardening | 01 | execute | 1 |
|
true |
|
|
Purpose: Make the archive format resist casual analysis by hiding the header structure, encrypting all metadata, and inserting random noise between data blocks. This is the encoder-side implementation that the Kotlin and Shell decoders will build against.
Output: Updated src/format.rs, src/archive.rs with full obfuscation pipeline. All cargo test pass including existing unit, golden vector, and round-trip integration tests.
<execution_context> @/home/nick/.claude/get-shit-done/workflows/execute-plan.md @/home/nick/.claude/get-shit-done/templates/summary.md </execution_context>
@.planning/PROJECT.md @.planning/ROADMAP.md @.planning/STATE.md @.planning/phases/06-obfuscation-hardening/06-RESEARCH.md @docs/FORMAT.md (Sections 9.1-9.3 and Section 10 for decode order) @src/format.rs @src/crypto.rs @src/archive.rs @src/key.rs Task 1: Add XOR header obfuscation and TOC encryption to format.rs src/format.rs Add the following to format.rs:- XOR_KEY constant (FORMAT.md Section 9.1):
pub const XOR_KEY: [u8; 8] = [0xA5, 0x3C, 0x96, 0x0F, 0xE1, 0x7B, 0x4D, 0xC8];
-
xor_header_buf() function that XORs a mutable byte slice (first 40 bytes) with the cyclic 8-byte key. XOR is its own inverse, so the same function encodes and decodes.
-
read_header_auto() function (replaces or wraps read_header for external use):
- Read 40 raw bytes.
- Check bytes 0-3 against MAGIC.
- If match: parse header normally from the buffer.
- If NO match: apply xor_header_buf to all 40 bytes, re-check magic. If still wrong, return error.
- Parse header fields from the (possibly de-XORed) buffer.
- This function should accept
&mut impl (Read + Seek)or work from a[u8; 40]buffer passed in. The simplest approach: accept a[u8; 40]buffer and return a Header (factoring out the parsing from read_header into a parse_header_from_buf helper).
-
write_header_to_buf() helper that serializes header to a
[u8; 40]buffer (instead of directly to writer), so the caller can XOR it before writing. -
write_toc_entry_to_vec() / serialize_toc() helper that serializes all TOC entries to a
Vec<u8>buffer, so the caller can encrypt the buffer. This can reuse write_toc_entry with a Vec writer. -
read_toc_from_buf() helper that parses TOC entries from a byte slice (using a Cursor), so the caller can pass in the decrypted TOC buffer.
Keep the existing read_header() and write_header() functions for backward compatibility with existing tests, but the new pack/unpack code will use the _buf variants.
Add unit tests:
- XOR round-trip: write header to buf, XOR, XOR again, verify identical to original.
- XOR changes magic: write header to buf, XOR, verify bytes 0-3 are NOT 0x00 0xEA 0x72 0x63.
- read_header_auto works with both plain and XOR'd headers. cd /home/nick/Projects/Rust/encrypted_archive && cargo test --lib format -- --nocapture 2>&1 | tail -5 Verify XOR_KEY constant matches FORMAT.md Section 9.1 exactly format.rs has XOR_KEY, xor_header_buf(), read_header_auto() with bootstrapping, and helper functions for buffer-based header/TOC serialization/parsing. All format unit tests pass.
pack() changes:
-
Generate decoy padding for each file:
let padding_after: u16 = rng.random_range(64..=4096);usingrand::Rng. Generate the random bytes too:let mut padding_bytes = vec![0u8; padding_after as usize]; rand::Fill::fill(&mut padding_bytes[..], &mut rng);. Store padding_after and padding_bytes in ProcessedFile struct (add fields). -
Compute data offsets accounting for padding: After computing toc_offset + toc_size (which will now be the ENCRYPTED toc size), compute data offsets as
current_offset += pf.encrypted_size + pf.padding_after as u32for each file. -
Serialize TOC entries to a buffer: Use the new serialize_toc helper. Include padding_after values in entries.
-
Encrypt serialized TOC: Generate
toc_iv = crypto::generate_iv(). Callcrypto::encrypt_data(&toc_plaintext, &KEY, &toc_iv). Thetoc_sizein the header becomesencrypted_toc.len() as u32. -
Build header: Set flags bits 1-3 in addition to bit 0 (compression). When all obfuscation is active and files are compressed, flags = 0x0F. Set toc_iv in header.
-
Compute toc_offset and data offsets:
toc_offset = HEADER_SIZE. Data block start =toc_offset + encrypted_toc_size. Then compute per-file data_offset accounting for preceding files'encrypted_size + padding_after. -
Serialize header to buffer and XOR: Use write_header_to_buf, then xor_header_buf on the resulting 40-byte buffer.
-
Write archive: XOR'd header bytes || encrypted TOC bytes || (for each file: ciphertext || padding_bytes).
unpack() changes:
- Read 40 bytes raw. Use read_header_auto (XOR bootstrapping).
- Check flags bit 1 (0x02) for TOC encryption. If set: seek to toc_offset, read toc_size bytes, decrypt with
crypto::decrypt_data(&encrypted_toc, &KEY, &header.toc_iv). Parse TOC from decrypted buffer using read_toc_from_buf. - If TOC not encrypted (backward compat): read TOC directly as before.
- Rest of unpack is unchanged -- each file uses data_offset from TOC entries, which already accounts for padding.
inspect() changes:
Apply the same header and TOC de-obfuscation as unpack. Factor out a shared read_archive_metadata() helper that returns (Header, Vec) with all de-obfuscation applied. Both unpack() and inspect() call this helper.
Important notes:
- Use
use rand::Rng;forrandom_range(). - Padding range 64..=4096 bytes per file.
- The
--no-compressflag behavior is unchanged. - Do NOT add a
--no-obfuscateflag yet (always obfuscate). cd /home/nick/Projects/Rust/encrypted_archive && cargo test 2>&1 | tail -10 Runcargo run -- pack test_file.txt -o /tmp/test.bin && xxd /tmp/test.bin | head -3and verify first 4 bytes are NOT 00 ea 72 63 pack() produces fully obfuscated archives (XOR header + encrypted TOC + decoy padding). unpack() and inspect() correctly de-obfuscate. Allcargo testpass including existing integration tests and round-trip tests (which now exercise the full obfuscation pipeline end-to-end).
<success_criteria>
- All three obfuscation features (FMT-06, FMT-07, FMT-08) are implemented in Rust archiver
- Flags byte is 0x0F for archives with compression + all obfuscation
- XOR bootstrapping allows decoders to detect both plain and obfuscated archives
- All
cargo testpass (0 failures) - Archives are unrecognizable by file/binwalk/strings </success_criteria>