Skip to content

Preserve PFX certificate chains in portable signing and add portable test PKI commands - #48

Merged
Marc-André Moreau (mamoreau-devolutions) merged 4 commits into
masterfrom
copilot/winget-static-feed-signing
Nov 12, 2026
Merged

Marc-André Moreau (mamoreau-devolutions) merged 4 commits into
masterfrom
copilot/winget-static-feed-signing

Conversation

@mamoreau-devolutions

Copy link
Copy Markdown
Contributor

Summary

While prototyping and validating end-to-end WinGet static feed (source2.msix) signing and HTTPS hosting with pinget + psign, we identified a PFX chain-dropping bug in portable signing and added first-class portable test PKI commands so isolated dev/test environments do not need ad-hoc PowerShell/.NET crypto scripts.

1. Fix: Preserve PFX certificate chains in portable CMS/PKCS#7 signing

  • Previously, load_pfx_cert_and_key in psign-portable-core, psign-tool code --pfx, and ZIP Authenticode signing extracted only the leaf certificate matching the private key and discarded all bundled issuer/CA certificates in the .pfx. As a result, AppxSignature.p7x (and other CMS signatures) omitted intermediate/issuer certificates (certificate_count = 1) unless --ac / --chain-cert was also passed separately.
  • Replaced single-certificate PFX extraction with psign_authenticode_trust::test_pki::parse_pfx_identity, which prefers explicit PKCS#12 localKeyId matching (falling back to public-key matching) and preserves all remaining certificates in the PFX as chain certificates.
  • Added DER-level certificate deduplication in crates/psign-sip-digest/src/pkcs7.rs when assembling SignedData.certificates so overlapping PFX + --ac / --chain-cert inputs never emit duplicate certificates.

2. Feature: Portable test PKI generation (psign-tool portable)

Added crates/psign-authenticode-trust/src/test_pki.rs and three portable CLI subcommands:

  • psign-tool portable generate-root-ca: Mints a self-signed X.509 v3 Root CA (BasicConstraints CA=true, KeyUsage KeyCertSign|CRLSign, SubjectKeyIdentifier) as .cer, .pem, .key, and/or .pfx without touching OS certificate stores.
  • psign-tool portable issue-code-signing-cert: Issues an X.509 v3 Code Signing leaf (BasicConstraints CA=false, KeyUsage DigitalSignature, EKU 1.3.6.1.5.5.7.3.3 + optional --eku-oid, SKI + AKI) chained to --ca-pfx or --ca-cert/--ca-key, bundling the full chain ([leaf, ca, ...]) into --pfx-out.
  • psign-tool portable issue-tls-server-cert: Issues an X.509 v3 TLS Server leaf (BasicConstraints CA=false, KeyUsage DigitalSignature|KeyEncipherment, EKU 1.3.6.1.5.5.7.3.1, SubjectAltName for repeatable --dns and --ip) chained to the same CA, bundling the full chain into --pfx-out and --pem-out.
  • Handles Windows X500DistinguishedName and RFC 4514 Subject DNs (S=, E=, quoted commas, and ASN.1 RDNSequence reversal so Windows X509Certificate2.Subject formats in exact MSIX Identity/@Publisher order), backdates notBefore by 24h for VM clock skew tolerance, clamps leaf notAfter to the issuing CA, and attaches PKCS#12 localKeyId attributes for Win32 PFXImportCertStore compatibility.

Testing

  • Added unit tests in crates/psign-authenticode-trust/src/test_pki.rs and end-to-end integration tests in tests/test_pki_msix.rs:
    • @response.rsp portable MSIX signing (--mode portable sign --pfx), portable verify-msix, portable inspect-pkcs7 (certificate_count == 2), and portable trust-verify-msix --trusted-ca (including wrong-CA rejection).
    • 3-tier Root -> SubCA -> Leaf PFX signing without --ac, proving intermediate CA chain embedding and verification against root.cer alone.
    • Negative tests rejecting non-CA issuers and SAN-less TLS server certificates.
    • Windows AppxSip / WinVerifyTrust (0x800B0109 untrusted-root-only check) and in-memory Windows CryptoAPI X509Chain (CustomRootTrust) validation for both Code Signing and TLS Server PFXs without modifying LocalMachine\Root.
  • Validated end-to-end on a Windows test VM (winget v1.29.380 + AppxSip/WinVerifyTrust + HTTPS TLS static feed) trusting a single generated root-ca.cer.
  • Updated docs/psign-cli-matrix.json, docs/psign-cli-matrix.md, docs/linux-signing-pipelines.md, and README.md.

…chains

- Add `psign_authenticode_trust::test_pki` for generating Windows/RFC 5280-compatible
  Root CA, Code Signing (`1.3.6.1.5.5.7.3.3`), and TLS Server (`1.3.6.1.5.5.7.3.1` +
  DNS/IP SANs) certificates, Windows `X500DistinguishedName` DN parsing/formatting
  (including `S=`, `E=`, and quoted commas), and PKCS#12 `.pfx` bundles with `localKeyId`.
- Preserve bundled CA chain certificates when parsing `.pfx` identities in
  `psign-portable-core` (`--mode portable sign --pfx`), `psign-tool code --pfx`, and
  ZIP Authenticode signing, and deduplicate CMS `SignedData.certificates` entries.
- Expose `psign-tool portable generate-root-ca`, `issue-code-signing-cert`, and
  `issue-tls-server-cert`, update the CLI parity matrix, and add end-to-end MSIX
  portable + Windows `AppxSip`/CryptoAPI `CustomRootTrust` tests without mutating
  host certificate stores.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
…res test import

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
… split, and code PE chain certs

- Fix X.500 DN attribute mapping so `SN` maps to `surname` (`2.5.4.4`) and
  `SERIALNUMBER` maps to `2.5.4.5`, matching RFC 4519 and Windows `CertStrToName`.
- Quote backslash-containing RDN values in `format_windows_dn`, treat `\` as literal
  inside `"..."` quotes (`CERT_X500_NAME_STR`), reject invalid unquoted backslash
  escapes, and preserve RFC 4514 escaped trailing spaces (`\ `) when trimming.
- Validate RFC 5280 `PrintableString` (including 2-character `C=`) and `IA5String`
  (`DC=`, `E=`) character sets in `parse_distinguished_name`, and decode `BMPString`
  RDNs in `format_windows_dn`.
- Extract production PKCS#12 parsing into `psign_authenticode_trust::pkcs12` (while
  re-exporting in `test_pki` for compatibility).
- Thread `--chain-cert` into `psign-tool code` PE/WinMD signing (both local and
  remote signers) alongside bundled PFX chain certificates.
- Add unit and Windows `.NET` + Win32 `PFXImportCertStore` (`powershell.exe`) tests
  covering DN edge cases, empty-password PBES2 PFX import, and `code --chain-cert` PE
  signing.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
…s-15

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
@mamoreau-devolutions
Marc-André Moreau (mamoreau-devolutions) merged commit 0e188ff into master Nov 12, 2026
36 checks passed
@mamoreau-devolutions
Marc-André Moreau (mamoreau-devolutions) deleted the copilot/winget-static-feed-signing branch November 12, 2026 19:21
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