---
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"
---

<objective>
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.
</objective>

<execution_context>
@/home/nick/.claude/get-shit-done/workflows/execute-plan.md
@/home/nick/.claude/get-shit-done/templates/summary.md
</execution_context>

<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
</context>

<tasks>

<task type="auto">
  <name>Task 1: Add XOR header obfuscation and TOC encryption to format.rs</name>
  <files>src/format.rs</files>
  <action>
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<u8>` 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.
  </action>
  <verify>
    <automated>cd /home/nick/Projects/Rust/encrypted_archive && cargo test --lib format -- --nocapture 2>&1 | tail -5</automated>
    <manual>Verify XOR_KEY constant matches FORMAT.md Section 9.1 exactly</manual>
  </verify>
  <done>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.</done>
</task>

<task type="auto">
  <name>Task 2: Update pack/unpack/inspect with full obfuscation pipeline</name>
  <files>src/archive.rs</files>
  <action>
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<TocEntry>) 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).
  </action>
  <verify>
    <automated>cd /home/nick/Projects/Rust/encrypted_archive && cargo test 2>&1 | tail -10</automated>
    <manual>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</manual>
  </verify>
  <done>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).</done>
</task>

</tasks>

<verification>
1. `cargo test` -- all existing unit, golden, and integration tests pass
2. `cargo run -- pack <files> -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
</verification>

<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 test` pass (0 failures)
- Archives are unrecognizable by file/binwalk/strings
</success_criteria>

<output>
After completion, create `.planning/phases/06-obfuscation-hardening/06-01-SUMMARY.md`
</output>
