Skip to content

Fix portable MSI signatures rejected by Windows - #37

Merged
Marc-André Moreau (mamoreau-devolutions) merged 3 commits into
Devolutions:masterfrom
nurfed1:pr/msi-digital-signature-ex
Sep 20, 2026
Merged

Marc-André Moreau (mamoreau-devolutions) merged 3 commits into
Devolutions:masterfrom
nurfed1:pr/msi-digital-signature-ex

Conversation

@nurfed1

@nurfed1 nurfed1 commented Sep 14, 2026

Copy link
Copy Markdown
Contributor

Summary

  • stage MsiDigitalSignatureEx before calculating the final MSI Authenticode digest
  • embed the PKCS#7 signature into that same prepared compound-file image
  • emit the Authenticode CMS algorithm identifiers and signed attributes expected by Windows
  • match Windows MSI SIP metadata and content traversal behavior
  • add native Windows verification using a valid MSI generated during CI

Problem

portable sign-msi could produce an MSI that passed psign's own digest verification but failed native Windows verification with TRUST_E_BAD_DIGEST.

The signing path calculated the final MSI digest before creating the MsiDigitalSignatureEx metadata-digest stream. Windows evaluates the prepared compound-file image containing that stream, so the digest embedded in SpcIndirectData did not describe the same signing state.

The generated CMS also differed from the Windows Authenticode profile in its algorithm identifiers and required signed attributes.

Change

Introduce a prepared MSI signing state that:

  1. calculates the package metadata digest;
  2. writes it to the root MsiDigitalSignatureEx stream;
  3. calculates the final Authenticode digest over that prepared image;
  4. embeds DigitalSignature into the same image after signing.

Both local certificate signing and remote Artifact Signing use this prepared state.

The MSI traversal now also matches Windows behavior for:

  • compound-file entry ordering;
  • root-only exclusion of the signature streams;
  • stream-size encoding;
  • pre-Unix FILETIME values.

Authenticode CMS generation now uses the Windows-compatible digest and RSA algorithm identifiers and includes SpcSpOpusInfo and SpcStatementType. Generic CMS signing retains its existing profile.

Tests

Unit tests cover:

  • staging MsiDigitalSignatureEx before the final digest;
  • rejecting missing or mismatched extended metadata;
  • deterministic preparation across supported digest algorithms;
  • Windows-compatible traversal ordering and metadata handling;
  • exclusion of only root signature streams;
  • the Authenticode signed-attribute profile.

The Windows workflow generates a valid no-op MSI from committed WiX source, verifies that Windows Installer can open it, signs copies using SignTool and portable psign, and requires native signtool verify /pa to accept both.

No signed MSI or product binary is committed as a fixture.

Validation

  • cargo fmt --all -- --check
  • cargo test -p psign-sip-digest --lib --locked
  • cargo test -p psign --test cli_pe_digest --locked
  • Windows, Unix, .NET tool, and PowerShell module workflows

Encode Authenticode digest identifiers with NULL parameters, use rsaEncryption for the signer algorithm, and include the standard SpcSpOpusInfo and SpcStatementType authenticated attributes. Keep generic CMS signing on its existing profile.
Write MsiDigitalSignatureEx before computing the final MSI SIP digest and embed the PKCS#7 into that same staged image. Match Windows metadata traversal details and cover the prepared signing state.
Generate a valid no-op WiX package, prove Windows Installer can open it, sign it with both SignTool and portable psign, and require native SignTool verification of both outputs. Reuse the fixture in exhaustive Windows parity.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🔵 Needs a closer look

It changes cryptographic CMS generation and Windows-specific SIP digest semantics across multiple signing paths.

Review effort: Balanced
Findings: None

What changed in this PR

Fixes portable MSI signing so Windows SIP accepts generated signatures.

Changes:

  • Stages MSI metadata before digesting and embedding signatures.
  • Aligns CMS attributes, algorithms, and MSI traversal with Windows.
  • Adds generated MSI fixtures and native Windows verification.
File Description
crates/​psign-sip-digest/​src/​msi_digest.rs Implements prepared MSI digest state and Windows-compatible traversal.
crates/​psign-sip-digest/​src/​pkcs7.rs Aligns Authenticode CMS algorithms and signed attributes.
crates/​psign-portable-core/​src/​lib.rs Uses prepared MSI state for unified portable signing.
crates/​psign-digest-cli/​src/​main.rs Updates local and remote MSI signing flows.
tests/​cli_pe_digest.rs Verifies MSI CMS profile details.
tests/​parity_signtool.rs Requires native acceptance of generated signatures.
tests/​fixtures/​msi-parity/​minimal.wxs Defines a minimal MSI fixture.
tests/​fixtures/​README.md Documents generated MSI parity fixtures.
scripts/​ci/​create-minimal-msi.ps1 Builds and validates the MSI fixture.
scripts/​ci/​run-exhaustive-parity-ci.ps1 Adds MSI generation to exhaustive parity.
.github/​workflows/​windows.yml Adds native Windows MSI verification.
docs/​ci-parity.md Documents the expanded CI workflow.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

@mamoreau-devolutions
Marc-André Moreau (mamoreau-devolutions) merged commit 6eae5f4 into Devolutions:master Sep 20, 2026
18 checks passed
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.

3 participants