diff --git a/README.md b/README.md new file mode 100644 index 0000000..c7063e8 --- /dev/null +++ b/README.md @@ -0,0 +1,87 @@ +# content-prep - Intune packages in pure go + +## Description + +This project aims to allow packaging Win32 apps for use with Intune. + +## Usage + +### Binary + +```shell +content-prep new --path "path/to/source" --setupFile "path/to/source/setupFile" --output "path/to/output" +``` + +### Docker +```shell +docker run ghcr.io/maxihafer/content-prep:latest \ + -v /path/to/source:/src \ + -v /path/to/output:/out \ + content-prep new --path /src --setupFile /src/setupFile --output /out +``` + + +## Motivation + Microsoft provides its closed-source [Content-Prep-Tool](https://github.com/microsoft/Microsoft-Win32-Content-Prep-Tool). For packaging Applications for intune. + Being just a checked in `.exe` file, it is neither possible to verify the code nor its behavior. +It is also not possible to run it on a non-Windows platform. + +There exist great open-source projects that implement the same functionality. Even though some of them work cross-platform, they still rely on `.NET` and `PowerShell` which is not ideal for my usecase of publishing intune packages from CI/CD. + +The great work of [@svrooij](https://github.com/svrooij) really helped alot in understanding the inner workings of Intune packages: + +- [Content-Prep](https://github.com/svrooij/ContentPrep) - `.NET/PowerShell` implementation of the Content-Prep functionality. +- [WinTuner](https://github.com/svrooij/wingetintune) - Very Sophisticated `PowerShell` Module for creating, deploying and managing Intune packages. +- [The Intune series in his blog](https://svrooij.io/2023/08/30/apps-intune/) - Great insights into the inner workings of `.intunewin` packages. Very well written and concise. + +## Intune package structure + +A `.intunewin` package is constructed as follows: +```shell +foo.intunewin (zip) +└── IntuneWinPackage + ├── Contents + │ └── IntunePackage.intunewin + └── Metadata + └── Detection.xml +``` + +### `IntunePackage.intunewin` +This is a encrypted `.zip` file containing the actual content of the package. More info can be found in the [Encryption Section](##encryption). + +### `foo.intunewin/Metadata/Detection.xml` +The `Detection.xml` file is read by Intune to extract all kinds of information about the package. It is a `XML` file with the following structure: +```xml + + IntunePackage.intunewin // This is the static name of the inner (encrypted) package + foo // Display name of the application + 216404340 // Size of the unencrypted content + foo.exe // Name of the setup file + + QX7BMsPzNDBSWv836oYT+gpxKlDYyPDm6C55XZKm9Z8= // Base64 encoded encryption key (32 byte) + Ysr5oRUvkgORiurV3zCf14jsj/baZOE/Xc6EEm/2wYA= // Base64 encoded HMAC key (32 byte) + /Nh7KHI5lYFyCTbGqBASPg== // Base64 encoded IV (16 byte) + PmGnbIzb6/N4pc3zZJF70+PYEAkXezR9Q6PaC4CzBdY= // Base64 encoded HMAC + ProfileVersion1 // Static encoding Profile used by Intune to verify package integrity + 47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU= // Base64 encoded SHA256 hash of the encrypted content + SHA256 // Hashing algorithm used + + +``` + +## Encryption Process + +1. The source folder (`src`) is zipped without any compression to a temporary file. +2. The output file (`out`) is created and its write position is forwarded by the length of the HMAC. +4. The IV is written to the output file (at position `0 + len(HMAC)`) as well as the HMAC itself. +5. The zipped `src` is then XORKeyStreamed into the output file starting at position `0 + len(HMAC) + len(IV)` as well as into the HMAC. +6. The HMAC is then calculated and written to the output file at position `0`. +7. The output file digest is calculated and written to the `Detection.xml` file. **_NOTE:_** As `src` is a zip archive, the digest changes from execution to execution, though the content is the same. This is due to the fact that the zip archive contains the file creation time in its header. + +The encryption key, HMAC key and IV are generated randomly at the start of the encryption process and stored in the `Detection.xml` file. + +The resulting structure of the encrypted file is as follows: + +| 32 Bytes | 16 Bytes | AES-CTR XORKeyStreamed Content | +|--------------------------------|----------|--------------------------------| +| HMAC of IV + Encrypted Content | IV | Encrypted Content | diff --git a/pkg/cryptostream/aesstream.go b/pkg/cryptostream/aesstream.go index 1fff79a..9c95dc4 100644 --- a/pkg/cryptostream/aesstream.go +++ b/pkg/cryptostream/aesstream.go @@ -11,7 +11,7 @@ import ( const BufferSize int = 2097152 const IvSize int = 16 -const HMACKeySize = 64 +const HMACKeySize = 32 // Encrypt the stream using the given AES-CTR and SHA256-HMAC key func Encrypt(in io.ReadSeeker, out io.WriteSeeker, keyAes []byte, iv []byte, hmacKey []byte) error { @@ -32,7 +32,7 @@ func Encrypt(in io.ReadSeeker, out io.WriteSeeker, keyAes []byte, iv []byte, hma } if len(hmacKey) != HMACKeySize { - return errors.New("invalid HMAC key length, expected 64 bytes") + return errors.New("invalid HMAC key length, expected 32 bytes") } hasher := HMAC.New(sha256.New, hmacKey)