Skip to content

Replace makeappx and signtool with in-process MSIX writing and psign-tool - #60

Merged
Marc-André Moreau (mamoreau-devolutions) merged 2 commits into
masterfrom
claude/pr-59-external-tools-13193a
Nov 3, 2026
Merged

Marc-André Moreau (mamoreau-devolutions) merged 2 commits into
masterfrom
claude/pr-59-external-tools-13193a

Conversation

@mamoreau-devolutions

Copy link
Copy Markdown
Contributor

Summary

Follow-up to #59. The WinGet source publishers no longer need the Windows SDK (makeappx.exe, signtool.exe) or, in Rust, PowerShell.

  • Packaging: both publishers write source2.msix in-process, in the exact AppxPackaging (makeappx) ZIP layout:

    • stored entries;
    • central directory entries with version-made-by 45 and zero external attributes;
    • ZIP64 end records;
    • an SHA-256 block map with a hash per 64 KiB block;
    • content types.

    Windows AppxSip rejects signatures over any other layout with TRUST_E_BAD_DIGEST. The Rust (msix.rs) and .NET (MsixPackageWriter.cs) writers are byte-identical for the same payload, and a .NET test pins hashes produced by the Rust writer.

  • Signing: done with the cross-platform psign-tool (Devolutions.Psign.Tool, 0.8.2 or later; 0.8.1's portable MSIX signatures fail Windows verification, fixed in Fix Windows TRUST_E_BAD_DIGEST on portable-signed MSIX/AppX; sign plain-ZIP packages psign#46).

    • All arguments, including the PFX password, go through a private, short-lived response file, so the password never appears on a command line. That matters on Linux, where /proc/*/cmdline is readable by other users, and for the .cmd launcher dotnet tool install creates on Windows.
  • Verification, after signing and in verify --destination:

    • the package digests;
    • the signer subject matches --publisher, formatted the way Windows formats certificate subjects;
    • the signer has the Code Signing EKU and is currently valid;
    • chain trust: the Windows store on Windows (as signtool verify /pa did), or new repeatable --trusted-ca anchors.
  • CLI: --makeappx/--signtool become --psign-tool; doctor JSON reports psignToolPath.

  • Portability: publish and verify now run off Windows, using the existing portable MSZIP decoders. build still compresses with the Windows Compression API.

  • CI, scripts and docs:

    • CI installs psign-tool 0.8.2.
    • The gate scripts take -PsignToolPath.
    • The interoperability check no longer needs makeappx.
    • Docs are updated.

Validation (local, Windows 11, SDK 10.0.26100)

  • Rust: publisher tests pass (19 unit + 26 conformance), as do the CLI tests; clippy and fmt are clean.
  • .NET: the solution builds; 80/80 publisher tests pass.
  • Interop: tests/publisher/run-production-conformance.ps1 passes: Rust and .NET reopen each other's state and verify each other's unsigned feeds.
  • Signed end to end with released psign-tool 0.8.2, both CLIs, via the plain exe and the dotnet-tool .cmd launcher, with a password containing quotes, @ and %PATH%:
    • signtool verify /pa and Get-AuthenticodeSignature report only the untrusted throwaway root, never a bad digest;
    • --trusted-ca publish and verify succeed;
    • a mismatched --publisher is rejected;
    • makeappx unpack accepts both signed and unsigned output.
  • Not yet run: the signed stock-WinGet acceptance job, which runs in CI on this PR. Note that NuGet had not yet listed the 0.8.2 platform packages (Devolutions.Psign.Tool.win-x64, …) shortly after the release. If they're still missing, the job's install step will fail until NuGet finishes indexing.

Notes

  • The full Rust and .NET source2.msix still differ, because their manifest text and generated logos differ (unchanged from before). Only the container layout is identical.
  • tests/publisher/build_v2_fixture.ps1 still uses makeappx on purpose. It's a manual, independent reference-fixture builder, and nothing calls it.

🤖 Generated with Claude Code

…tool

Both repository publishers now write source2.msix themselves in the exact
AppxPackaging (makeappx) ZIP layout and sign and verify it with the
cross-platform psign-tool (Devolutions.Psign.Tool 0.8.2 or later). The
Windows SDK and the Rust PowerShell certificate check are no longer needed.

- Signing passes all psign-tool arguments, including the PFX password,
  through a private response file instead of the command line.
- Verification checks package digests, that the signer subject matches
  --publisher with the Code Signing EKU, and chain trust: the Windows
  store on Windows, or --trusted-ca anchors (repeatable).
- --makeappx/--signtool become --psign-tool; doctor reports psignToolPath.
- publish and verify decode MSZIP portably off Windows; build still
  compresses with the Windows Compression API.
- CI installs psign-tool 0.8.2; gate scripts take -PsignToolPath.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Run .cmd/.bat psign-tool shims through cmd.exe /s /c with paired
  quotes in .NET, and reject paths cmd.exe would still expand.
- Drop the .NET pre-sign PFX check so both publishers rely on the
  post-sign signer check; reject publishers with surrounding whitespace.
- Align .NET --timestamp-url validation with Rust (signed only, HTTP(S),
  no whitespace).
- Make Rust psign workspaces unique per call, close child stdin, and
  render non-string DN values as unquoted DER hex.
- Report a missing explicit --psign-tool path as such.
- Write the same AppxManifest.xml and logos from Rust as from .NET, and
  check in production conformance that packages match outside the index.
- Add Rust tests for certificate checks, response files, redaction and
  batch-shim invocation.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@mamoreau-devolutions
Marc-André Moreau (mamoreau-devolutions) merged commit da4f34f into master Nov 3, 2026
14 checks passed
@mamoreau-devolutions
Marc-André Moreau (mamoreau-devolutions) deleted the claude/pr-59-external-tools-13193a branch November 3, 2026 14:14
Marc-André Moreau (mamoreau-devolutions) pushed a commit that referenced this pull request Nov 5, 2026
## 🤖 New release

* `pinget-core`: 0.14.0 -> 0.14.1 (✓ API compatible changes)
* `pinget-publisher`: 0.14.0

<details><summary><i><b>Changelog</b></i></summary><p>

## `pinget-core`

<blockquote>

##
[0.14.1](pinget-core-v0.14.0...pinget-core-v0.14.1)
- 2026-11-05

### Other

- Support machine scope for zip and portable installers
([#61](#61))
- Replace makeappx and signtool with in-process MSIX writing and
psign-tool ([#60](#60))
- Add native WinGet preindexed repository publishers in Rust and C#
([#59](#59))
- Fixes uninstall dispatch for EXE/Burn ARP entries whose registry
subkey is GUID-shaped.
([#57](#57))
</blockquote>

## `pinget-publisher`

<blockquote>

##
[0.14.0](https://github.com/Devolutions/pinget/releases/tag/pinget-publisher-v0.14.0)
- 2026-11-05

### Other

- Replace makeappx and signtool with in-process MSIX writing and
psign-tool ([#60](#60))
- Add native WinGet preindexed repository publishers in Rust and C#
([#59](#59))
</blockquote>


</p></details>

---
This PR was generated with
[release-plz](https://github.com/release-plz/release-plz/).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

1 participant