---
phase: 01-foundation
plan: 03
type: execute
wave: 1
depends_on: []
files_modified:
- imptune/generators/__init__.py
- imptune/generators/intunewin_builder.py
- tests/test_intunewin.py
autonomous: true
requirements:
- INFRA-02
must_haves:
truths:
- "A Python function produces a valid .intunewin file from a source directory and setup file name"
- "The .intunewin file contains an outer ZIP with IntuneWinPackage/Contents/IntunePackage.intunewin and IntuneWinPackage/Metadata/Detection.xml"
- "The encrypted blob uses the correct byte layout: HMAC-SHA256 (32 bytes) + IV (16 bytes) + AES-256-CBC ciphertext"
- "Detection.xml contains correct EncryptionKey, MacKey, InitializationVector, Mac, FileDigest values that match the actual encryption"
- "The inner ZIP uses DEFLATE compression and the outer ZIP uses STORED compression"
artifacts:
- path: "imptune/generators/intunewin_builder.py"
provides: "Python-native .intunewin file assembler using pycryptodome"
exports: ["build_intunewin"]
min_lines: 60
- path: "tests/test_intunewin.py"
provides: "Byte-level validation tests for .intunewin format"
min_lines: 80
key_links:
- from: "imptune/generators/intunewin_builder.py"
to: "pycryptodome"
via: "from Crypto.Cipher import AES"
pattern: "Crypto\\.Cipher"
- from: "imptune/generators/intunewin_builder.py"
to: "zipfile"
via: "stdlib zipfile for inner and outer ZIPs"
pattern: "zipfile\\.ZipFile"
---
Implement the Python-native .intunewin file builder as a time-boxed spike. This module generates .intunewin packages using AES-256-CBC encryption with HMAC-SHA256, producing the exact byte layout Intune expects.
Purpose: Validate the highest-risk unknown in the project — can Python generate a .intunewin file that Intune accepts? This spike runs independently of the web app and produces a standalone generator module reused in Phase 5. Supports INFRA-02 (no external binary dependencies like IntuneWinAppUtil.exe).
Output: A tested build_intunewin() function and comprehensive byte-level validation tests.
@C:/Users/SebastienQUEROL/.claude/get-shit-done/workflows/execute-plan.md
@C:/Users/SebastienQUEROL/.claude/get-shit-done/templates/summary.md
@.planning/PROJECT.md
@.planning/ROADMAP.md
@.planning/STATE.md
@.planning/phases/01-foundation/01-CONTEXT.md
@.planning/phases/01-foundation/01-RESEARCH.md
Task 1: Implement .intunewin builder with byte-level tests
imptune/generators/__init__.py,
imptune/generators/intunewin_builder.py,
tests/test_intunewin.py
- test_output_is_valid_zip: build_intunewin() output file is a valid ZIP archive
- test_outer_zip_structure: outer ZIP contains exactly IntuneWinPackage/Contents/IntunePackage.intunewin and IntuneWinPackage/Metadata/Detection.xml
- test_outer_zip_stored: outer ZIP entries use ZIP_STORED compression (no extra compression on encrypted content)
- test_detection_xml_valid: Detection.xml is valid XML with ApplicationInfo root element in the correct namespace (http://schemas.microsoft.com/IntuneWin)
- test_detection_xml_fields: Detection.xml contains Name, UnencryptedContentSize, FileName, SetupFile, and full EncryptionInfo with all 8 sub-elements (EncryptionKey, MacKey, InitializationVector, Mac, MacAlgorithm, ProfileIdentifier, FileDigest, FileDigestAlgorithm)
- test_encrypted_blob_layout: the encrypted blob starts with 32 bytes (HMAC) + 16 bytes (IV) + remainder (ciphertext); total length = 48 + ciphertext length
- test_iv_is_16_bytes: IV extracted from Detection.xml base64-decodes to exactly 16 bytes (NOT 32 — critical per RESEARCH.md)
- test_encryption_key_is_32_bytes: EncryptionKey from Detection.xml base64-decodes to exactly 32 bytes
- test_mac_key_is_32_bytes: MacKey from Detection.xml base64-decodes to exactly 32 bytes
- test_hmac_matches: HMAC-SHA256 computed from MacKey over ciphertext matches the first 32 bytes of the blob AND the Mac value in Detection.xml
- test_decryption_roundtrip: using EncryptionKey and IV from Detection.xml, decrypt the ciphertext, unpad, and verify the result is a valid DEFLATE-compressed ZIP containing the original source files
- test_file_digest_matches: FileDigest in Detection.xml matches SHA256 of the decrypted plaintext ZIP
- test_unencrypted_content_size: UnencryptedContentSize in Detection.xml matches the byte length of the decrypted plaintext ZIP
- test_setup_file_in_detection_xml: SetupFile element matches the setup_file argument passed to build_intunewin
**imptune/generators/intunewin_builder.py**:
Implement build_intunewin(source_dir: str, setup_file: str, output_path: str) -> None following the skeleton from RESEARCH.md Pattern 3, with these specifics:
1. Create inner ZIP (DEFLATE compression) of all files in source_dir, preserving relative paths
2. Generate random keys: aes_key = os.urandom(32), mac_key = os.urandom(32), iv = os.urandom(16) — IV MUST be 16 bytes per the critical correction in RESEARCH.md
3. Encrypt with AES-256-CBC: cipher = AES.new(aes_key, AES.MODE_CBC, iv), ciphertext = cipher.encrypt(pad(plaintext, AES.block_size))
4. Compute HMAC-SHA256 of ciphertext using mac_key
5. Assemble encrypted blob: hmac_digest (32 bytes) + iv (16 bytes) + ciphertext
6. Compute file_digest = SHA256 of plaintext (the inner ZIP bytes before encryption)
7. Build Detection.xml with all required fields (see RESEARCH.md for exact schema). Use xml.etree.ElementTree for building and xml.dom.minidom for pretty printing. Set xmlns="http://schemas.microsoft.com/IntuneWin" on ApplicationInfo root.
8. Build outer ZIP (STORED compression) with two entries: IntuneWinPackage/Contents/IntunePackage.intunewin (the encrypted blob) and IntuneWinPackage/Metadata/Detection.xml
9. All base64 values in Detection.xml use standard base64 encoding (base64.b64encode)
**tests/test_intunewin.py**:
- Create a tmp_path fixture with a small test source directory (2-3 small text files, one named "install.ps1")
- Call build_intunewin(source_dir, "install.ps1", output_path) to generate the file
- Implement all tests from the behavior list above
- For the decryption roundtrip: extract EncryptionKey and IV from Detection.xml, use AES.new(key, AES.MODE_CBC, iv) to decrypt, unpad the result, verify it's a valid ZIP containing the original files
- For HMAC verification: extract MacKey from Detection.xml, compute hmac.new(mac_key, ciphertext, hashlib.sha256).digest(), compare to first 32 bytes of blob AND to Mac value in Detection.xml
The tests serve as the format specification — if they pass, the byte layout is correct. The only remaining validation is a real Intune upload (manual, Phase 5 gate).
cd C:/Users/SebastienQUEROL/Documents/projets/ImpTune && pip install pycryptodome -q && python -m pytest tests/test_intunewin.py -x -v
- All 14 byte-level tests pass
- IV is confirmed 16 bytes (not 32)
- Decryption roundtrip succeeds: encrypt then decrypt recovers original files
- HMAC verification succeeds: computed HMAC matches blob header and Detection.xml Mac field
- Outer ZIP structure matches Intune's expected layout exactly
- Detection.xml has correct namespace and all required fields
- `python -m pytest tests/test_intunewin.py -x -v` — all 14 tests pass
- `python -c "from imptune.generators.intunewin_builder import build_intunewin; print('Builder importable')"` — no import errors
- The .intunewin file produced can be opened as a ZIP and inspected manually (outer structure visible)
- build_intunewin() produces a file with the exact byte layout Intune expects
- All crypto operations use correct key/IV sizes (32/32/16 bytes)
- HMAC and decryption roundtrip verified programmatically
- Detection.xml contains all 8 EncryptionInfo sub-elements with correct values
- Module is standalone — no dependency on the web framework or database