--- phase: 06-obfuscation-hardening plan: 01 type: execute wave: 1 depends_on: [] files_modified: - src/format.rs - src/archive.rs - src/crypto.rs - tests/golden_vectors.rs autonomous: true requirements: - FMT-06 - FMT-07 - FMT-08 must_haves: truths: - "Rust archiver pack() produces archives with XOR-obfuscated headers (magic bytes not visible in raw hex)" - "Rust archiver pack() encrypts the TOC with AES-256-CBC using a random toc_iv stored in header" - "Rust archiver pack() inserts random decoy padding between data blocks" - "Rust unpack() and inspect() correctly decode obfuscated archives (XOR de-obfuscation + TOC decryption)" - "All existing cargo test pass (unit tests + integration tests + golden vectors)" - "Flags byte is 0x0F when compression + all 3 obfuscation features are active" artifacts: - path: "src/format.rs" provides: "XOR_KEY constant, xor_header_buf() function, read_header_auto() with XOR bootstrapping" contains: "XOR_KEY" - path: "src/archive.rs" provides: "Updated pack() with TOC encryption + decoy padding + XOR header; updated unpack()/inspect() with de-obfuscation" contains: "xor_header_buf" - path: "src/crypto.rs" provides: "generate_iv (unchanged) used for toc_iv" key_links: - from: "src/archive.rs pack()" to: "src/format.rs xor_header_buf()" via: "XOR applied to 40-byte header buffer after write_header" pattern: "xor_header_buf" - from: "src/archive.rs pack()" to: "src/crypto.rs encrypt_data()" via: "TOC plaintext buffer encrypted with toc_iv" pattern: "encrypt_data.*toc" - from: "src/archive.rs unpack()/inspect()" to: "src/format.rs" via: "XOR bootstrapping on header read, then TOC decryption" pattern: "xor_header_buf|decrypt_data" --- Implement all three obfuscation features (XOR headers, encrypted TOC, decoy padding) in the Rust archiver and unpacker, with all existing tests passing. 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. @/home/nick/.claude/get-shit-done/workflows/execute-plan.md @/home/nick/.claude/get-shit-done/templates/summary.md @.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: 1. **XOR_KEY constant** (FORMAT.md Section 9.1): ```rust pub const XOR_KEY: [u8; 8] = [0xA5, 0x3C, 0x96, 0x0F, 0xE1, 0x7B, 0x4D, 0xC8]; ``` 2. **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. 3. **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). 4. **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. 5. **write_toc_entry_to_vec() / serialize_toc()** helper that serializes all TOC entries to a `Vec` buffer, so the caller can encrypt the buffer. This can reuse write_toc_entry with a Vec writer. 6. **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. Task 2: Update pack/unpack/inspect with full obfuscation pipeline src/archive.rs Update archive.rs to implement all three obfuscation features. Follow the encoder order from 06-RESEARCH.md: **pack() changes:** 1. **Generate decoy padding** for each file: `let padding_after: u16 = rng.random_range(64..=4096);` using `rand::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). 2. **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 u32` for each file. 3. **Serialize TOC entries to a buffer**: Use the new serialize_toc helper. Include padding_after values in entries. 4. **Encrypt serialized TOC**: Generate `toc_iv = crypto::generate_iv()`. Call `crypto::encrypt_data(&toc_plaintext, &KEY, &toc_iv)`. The `toc_size` in the header becomes `encrypted_toc.len() as u32`. 5. **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. 6. **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`. 7. **Serialize header to buffer and XOR**: Use write_header_to_buf, then xor_header_buf on the resulting 40-byte buffer. 8. **Write archive**: XOR'd header bytes || encrypted TOC bytes || (for each file: ciphertext || padding_bytes). **unpack() changes:** 1. Read 40 bytes raw. Use read_header_auto (XOR bootstrapping). 2. 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. 3. If TOC not encrypted (backward compat): read TOC directly as before. 4. 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;` for `random_range()`. - Padding range 64..=4096 bytes per file. - The `--no-compress` flag behavior is unchanged. - Do NOT add a `--no-obfuscate` flag yet (always obfuscate). cd /home/nick/Projects/Rust/encrypted_archive && cargo test 2>&1 | tail -10 Run `cargo run -- pack test_file.txt -o /tmp/test.bin && xxd /tmp/test.bin | head -3` and 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. All `cargo test` pass including existing integration tests and round-trip tests (which now exercise the full obfuscation pipeline end-to-end). 1. `cargo test` -- all existing unit, golden, and integration tests pass 2. `cargo run -- pack -o /tmp/obf.bin` produces an archive where `xxd /tmp/obf.bin | head -3` shows no recognizable magic bytes 3. `cargo run -- inspect /tmp/obf.bin` correctly displays metadata after de-obfuscation 4. `cargo run -- unpack /tmp/obf.bin -o /tmp/obf_out/` extracts files byte-identically to originals 5. `binwalk /tmp/obf.bin` and `file /tmp/obf.bin` show no recognized signatures - 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 test` pass (0 failures) - Archives are unrecognizable by file/binwalk/strings After completion, create `.planning/phases/06-obfuscation-hardening/06-01-SUMMARY.md`