Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 32 additions & 0 deletions crates/psign-digest-cli/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2370,6 +2370,17 @@ enum Command {
#[arg(long, value_enum, default_value_t = InspectInputKind::Pe)]
input: InspectInputKind,
},
/// Inspect standalone PKCS#7 / P7X bytes as JSON.
///
/// Accepts PKCS#7 `ContentInfo`, bare `SignedData`, or AppX `AppxSignature.p7x`
/// files with a `PKCX` wrapper.
InspectPkcs7 { path: PathBuf },
/// Strip an AppX `AppxSignature.p7x` `PKCX` wrapper and write the inner PKCS#7 DER.
ExtractPkcxPkcs7 {
path: PathBuf,
#[arg(long, value_name = "PATH")]
output: Option<PathBuf>,
},
/// Validate JSON metadata shape for Microsoft Artifact Signing (`Endpoint`, `CodeSigningAccountName`, `CertificateProfileName`; optional `ExcludeCredentials` string array). No network / no signing.
///
/// Reads **`--path`** or stdin when omitted (use `-` for stdin explicitly).
Expand Down Expand Up @@ -5153,6 +5164,27 @@ where
};
println!("{json}");
}
Command::InspectPkcs7 { path } => {
let bytes = std::fs::read(&path).with_context(|| format!("read {}", path.display()))?;
let json = serde_json::to_string_pretty(&inspect_authenticode_pkcs7_der(&bytes)?)?;
println!("{json}");
}
Command::ExtractPkcxPkcs7 { path, output } => {
let bytes = std::fs::read(&path).with_context(|| format!("read {}", path.display()))?;
let inner = pkcs7_wire::strip_pkcx_p7x_wrapper(&bytes).ok_or_else(|| {
anyhow!(
"extract-pkcx-pkcs7 {}: missing PKCX wrapper",
path.display()
)
})?;
match output.as_ref() {
Some(p) => std::fs::write(p, inner)
.with_context(|| format!("write PKCS#7 to {}", p.display()))?,
None => std::io::stdout()
.write_all(inner)
.context("write PKCS#7 to stdout")?,
}
}
Command::ArtifactSigningMetadataCheck { path } => {
run_artifact_signing_metadata_check(path)?;
}
Expand Down
10 changes: 9 additions & 1 deletion crates/psign-sip-digest/src/pkcs7_wire.rs
Original file line number Diff line number Diff line change
Expand Up @@ -83,8 +83,16 @@ pub fn pkcs7_outer_sequence_prefix(data: &[u8]) -> Option<&[u8]> {
data.get(..n)
}

/// Normalize detached PKCS#7 blobs: bare `SignedData` sequences are wrapped as PKCS#7 `ContentInfo`.
/// Strip the Windows AppX/AppInstaller **PKCX** wrapper used by standalone
/// `AppxSignature.p7x` files, returning the inner PKCS#7 DER.
pub fn strip_pkcx_p7x_wrapper(data: &[u8]) -> Option<&[u8]> {
data.strip_prefix(b"PKCX")
}

/// Normalize detached PKCS#7 blobs: PKCX-wrapped AppX `AppxSignature.p7x` files
/// are unwrapped, and bare `SignedData` sequences are wrapped as PKCS#7 `ContentInfo`.
pub fn normalize_pkcs7_der_for_authenticode(sig_blob: &[u8]) -> Cow<'_, [u8]> {
let sig_blob = strip_pkcx_p7x_wrapper(sig_blob).unwrap_or(sig_blob);
let Some(inner) = tlv_outer_sequence_payload(sig_blob) else {
return Cow::Borrowed(sig_blob);
};
Expand Down
39 changes: 36 additions & 3 deletions docs/gap-analysis-signing-platforms.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ This inventory starts from the in-tree supported formats, then expands to inbox
| **Cleartext AppX/MSIX** (`.appx`, `.msix`, `.appxbundle`, `.msixbundle`, `.appxupload`, `.msixupload`) | Sign/verify with AppX client data and dlib bridge. | Remaining native parity failures can occur around `SignerSignEx3` AppX glue, publisher binding, sealing, and package constraints. | `verify-msix` digest consistency; `msix-manifest-info` / `msix-set-publisher`; native-shaped portable Artifact Signing final signing for flat `.appx` / `.msix` packages with `AppxSignature.p7x` / `PKCX` embedding and optional RFC3161 timestamping; guarded `psign-tool code` prepare execution signs nested PE/package entries, updates `AppxManifest.xml` Publisher from `--publisher-name`, regenerates `AppxBlockMap.xml`, propagates publisher updates into nested packages inside upload/bundle containers, and rejects already-final-signed `AppxSignature.p7x` packages before final AppX SIP signing. | Bundle/upload final signing, encrypted packages, manifest publisher-vs-signer policy, and full AppX package policy remain pending. |
| **Encrypted AppX/MSIX** (`.eappx`, `.emsix`, `.eappxbundle`, `.emsixbundle`) | Delegates to OS `EappxSip*` / `EappxBundleSip*`. | No in-tree understanding beyond OS delegation and parity fixtures. | Explicitly rejected by `verify-msix`, MSIX metadata helpers, and `psign-tool code` with Windows AppxSip OS-delegation diagnostics. | Encrypted package crypto/header handling is absent; ZIP-only digest logic is insufficient. |
| **AppX extension SIP chain** | Delegates to installed `ExtensionsSip*` providers. | No bundled/provider-specific parity coverage; behavior depends on optional third-party SIP DLLs. | Not implemented. | No extension-provider discovery, DLL contract, or portable provider model. |
| **Standalone P7X / PKCX** (`.p7x`) | OS `P7xSip*` can participate when registered; real package signatures are produced as `AppxSignature.p7x` inside signed AppX/MSIX packages. | Direct standalone `.p7x` signing is rejected by current SignTool; first-class commands for extracting/interpreting PKCX remain absent. | Raw PKCS#7 inspection/trust primitives may apply after extraction. | No dedicated PKCX/P7X container command or portable PKCX header handling. |
| **Standalone P7X / PKCX** (`.p7x`) | OS `P7xSip*` can participate when registered; real package signatures are produced as `AppxSignature.p7x` inside signed AppX/MSIX packages. | Direct standalone `.p7x` signing is rejected by current SignTool. | `inspect-pkcs7` accepts raw PKCS#7, bare `SignedData`, and PKCX-wrapped `AppxSignature.p7x`; `extract-pkcx-pkcs7` strips the PKCX wrapper; detached trust remains available through `trust-verify-detached` when caller supplies the detached content. | No standalone `.p7x` signing/export flow mapped to native `/p7*` switches. |
| **PowerShell-class scripts** (`.ps1`, `.psm1`, `.psd1`, `.ps1xml`, `.psc1`, `.cdxml`, `.mof`) | Sign/verify through `pwrshsip.dll`; parity fixtures cover `.ps1`, `.psm1`, `.psd1`. | Need parity fixtures and format detection for `.ps1xml`, `.psc1`, `.cdxml`, `.mof`. | `verify-script` digest consistency for PowerShell-style markers. | No signing/embed; digest remains heuristic for every malformed block and encoding edge case. |
| **WSH scripts** (`.js`, `.jse`, `.vbs`, `.vbe`, `.wsf`) | Sign/verify through `wshext.dll`; parity fixtures cover `.js`, `.vbs`, `.wsf`. | Need `.jse` and `.vbe` parity coverage. | `verify-script` digest consistency for WSH markers. | No signing/embed; native COM text conversion and unusual encodings may diverge. |
| **Office / VBA macro projects** | Delegates to installed `mso.dll` / `VBE7.DLL` SIP when present. | No direct Office/VBA CLI affordance or parity fixture set; depends on installed Office components. | Not implemented. | No VBA project graph hashing; likely needs VBE7/Office FFI or permanent OS delegation. |
Expand All @@ -72,7 +72,7 @@ The committed corpus already includes generated unsigned and signed vectors for
| **MST transforms** (`.mst`) | Unsigned generated transform exists; signed native output is retained in skipped corpus rows because `/pa` verification rejects it. | A verifiable signed `.mst` fixture if native Windows Installer policy supports one, or deeper tests around the documented reject. |
| **Encrypted AppX/MSIX** (`.eappx`, `.eappxbundle`, `.emsix`, `.emsixbundle`) | Unsigned/placeholder negative files exist. | Real signed encrypted package fixtures, if the project decides to test OS-only Windows delegation. |
| **WSH component scripts** (`.wsc`) | Unsigned probe files exist and native SignTool rejection is recorded; `.jse` / `.vbe` have signed generated probes. | Signed `.wsc` fixture if a supported provider/tooling path is identified. |
| **Standalone P7X / PKCX** (`.p7x`) | Unsigned direct-signing probe exists and native SignTool rejection is recorded; a real `AppxSignature.p7x` is extracted from a signed MSIX fixture. | First-class PKCX/P7X parsing/verification behavior remains an implementation gap. |
| **Standalone P7X / PKCX** (`.p7x`) | Unsigned direct-signing probe exists and native SignTool rejection is recorded; a real `AppxSignature.p7x` is extracted from a signed MSIX fixture. | PKCX extraction and standalone PKCS#7/P7X inspection are covered by portable CLI tests; native-shaped standalone `.p7x` signing/export remains uncovered. |
| **App Installer descriptors** (`.appinstaller`) | Unsigned descriptor exists and native direct-signing rejection is recorded; a real SignTool `/p7` companion signature is generated for detached verification coverage. | Companion PKCS#7 generation and policy checks remain implementation gaps. |
| **Optional-provider / XML signing surfaces** (`.application`, `.manifest`, `.vsto`, `.deploy`) | Unsigned probe files exist and native SignTool rejection/provider-unavailable outcomes are recorded. | Signed ClickOnce/VSTO-style fixtures and tool-specific signing metadata. |
| **Office macro containers** (`.docm`, `.xlsm`, `.pptm`, `.xlam`) | Unsigned probe files exist. | Signed Office/VBA macro-project fixtures generated with installed Office/VBE SIP, plus verification expectations. |
Expand All @@ -96,6 +96,39 @@ The committed corpus already includes generated unsigned and signed vectors for

---

## Top 3 gaps worth filling next

These are the highest-leverage gaps after comparing the native switch matrix, portable lifecycle coverage, package-orchestration work, and fixture corpus. The stable IDs are mirrored in `psign-cli-matrix.json` as `top_gap_ids`; implementation details should remain here to avoid overloading the CLI switch matrix with roadmap prose.

| Priority | Gap id | Current state | Fill plan |
|----------|--------|---------------|-----------|
| 1 | `portable-msix-bundle-upload-final-signing` | Flat cleartext `.appx` / `.msix` portable Artifact Signing exists; `psign-tool code` can prepare nested bundle/upload contents and regenerate manifests/block maps, but final bundle/upload signing and encrypted packages remain outside the portable embedder. | Reuse the flat MSIX signer and publisher/block-map preparation, add bundle/upload traversal that signs nested packages before the outer container, define explicit rejection for encrypted packages, and add fixtures for unsigned bundle/upload -> signed verify/tamper cases. |
| 2 | `catalog-driver-package-authoring` | Portable `sign-catalog` can author generic CTL catalogs and `verify-catalog-member` can check explicit file membership, while Windows mode can sign/verify existing catalogs and mutate catalog databases. | Extend catalog authoring toward MakeCat/Inf2Cat/New-FileCatalog-compatible member metadata, add driver/INF policy diagnostics separately from generic catalogs, and grow the corpus with psign-authored plus native-authored driver-package catalogs. |
| 3 | `wdac-ci-policy-signing` | Detached PKCS#7 and catalog primitives exist, but WDAC / Code Integrity policy signing is documented only as adjacent backlog. | Define policy-file detection and expected signature container shape, route signing through existing detached PKCS#7/catalog CMS helpers, then add verification diagnostics that distinguish CMS validity from Windows deployment/CI policy acceptance. |

These three outrank smaller parity refinements such as stricter timestamp routing or OCSP/CRL policy hardening because they unlock whole user workflows rather than polishing already-available verification paths.

## Additional gap candidates worth filling

The next tier is still worth tracking because each item either unlocks a recognizable signing workflow, removes a common Windows-only dependency, or closes an ambiguity that would otherwise make portable verification hard to trust. They are not mirrored in `psign-cli-matrix.json` yet because they are roadmap candidates rather than stable top priorities.

| Rank | Gap id | Why it is worth filling | First useful slice |
|------|--------|-------------------------|--------------------|
| 4 | `standalone-pkcx-p7x-tooling` | Native SignTool users encounter standalone PKCS#7 / PKCX artifacts through `/p7`, `/p7ce`, `/p7co`, `/p7u`, and catalog-style detached workflows. The first standalone slice is implemented: `inspect-pkcs7` and `extract-pkcx-pkcs7` make raw PKCS#7 and AppX `PKCX` files first-class portable inputs. | Grow into explicit detached verify ergonomics and signing/export flows that map cleanly to the native `/p7*` switches. |
| 5 | `office-vba-signing-verification` | Office/VBA macro signing is a real Authenticode SIP surface that remains a practical Windows-only island; even diagnostic coverage would help teams inventory and migrate signed macro assets. | Start with Windows-mode detection, verify, and remove diagnostics around the `mso.dll` SIP, then document portable rejection and fixture requirements before attempting portable embed support. |
| 6 | `split-digest-signing-pipeline` | `/dg`, `/ds`, `/di`, and `/dxml` workflows are important for HSM, air-gapped, and service-mediated signing where the machine that hashes is not the machine that applies the signature. | Normalize one portable digest -> external signature -> ingest path for PE first, then reuse it for CAB/MSI/catalog as their portable embedders mature. |
| 7 | `portable-trust-policy-hardening` | Current portable trust is intentionally anchor-directory based; production verification often also needs disallowed CTLs/STLs, EKU/application-policy rules, revocation depth, OCSP/CRL edge cases, and TrustedPublisher semantics. | Add policy switches and diagnostics without pretending to be the OS trust store, prioritizing disallowed roots/intermediates and AuthRoot-derived pin/rule fixtures. |
| 8 | `clickonce-mage-compatible-signing` | ClickOnce deployments are still common in enterprise Windows estates and have their own manifest canonicalization, timestamping, and policy expectations beyond generic XML/package handling. | Implement detect/inspect/verify parity first, then add Mage-compatible signing and timestamping only after native-vs-portable fixture parity is clear. |
| 9 | `nuget-repository-signature-policy` | NuGet packages have author signatures, repository signatures, countersignatures, timestamp policy, and package-source trust decisions that go beyond the existing package digest primitives. | Extend inspection to distinguish author vs repository signatures and add policy diagnostics before attempting repository-signature authoring. |
| 10 | `vsix-timestamping-and-policy` | VSIX signing support without timestamp/policy parity leaves long-lived extension distribution workflows incomplete. | Add timestamp mutation and verify diagnostics for existing signed VSIX packages, with tamper/expiration fixtures. |
| 11 | `appinstaller-policy-verify` | App Installer files are small but security-sensitive orchestration manifests; portable hashing alone does not answer whether an update/feed policy is safe or acceptable. | Add policy-focused inspection and verify diagnostics for signed `.appinstaller` manifests, separate from MSIX package signing. |
| 12 | `encrypted-msix-delegation-fixtures` | Encrypted MSIX/AppX packages should probably remain a Windows/decryption-bound path, but explicit fixture-backed detection prevents confusing portable failures and regression drift. | Add corpus coverage and diagnostics that distinguish encrypted-package rejection from malformed-package failures, then route Windows-mode operations to the OS where possible. |
| 13 | `cab-replacement-multivolume-mutation` | CAB support covers important single-volume signing paths, but setup media and legacy installers can require replacement/multivolume behavior and removal parity. | Add negative/diagnostic coverage for unsupported CAB layouts first, then implement replacement and remove flows for the safest subset. |
| 14 | `msi-policy-expansion` | MSI/MSP signatures have policy branches such as `MsiDigitalSignatureEx` and installer-specific verification behavior that are distinct from generic PKCS#7 validity. | Extend MSI inspection to report signature-table variants and policy-relevant metadata before adding stricter trust decisions. |
| 15 | `extension-sip-provider-model` | Windows can delegate arbitrary subject formats to installed SIP providers, while portable mode only supports built-in Rust format handlers. | Define a narrow provider interface for digest/inspect/verify experiments, but keep signing gated until deterministic fixtures and security boundaries are understood. |

---

## Native Windows SDK `signtool.exe`

**Strengths:** Full Authenticode lifecycle — **sign**, **verify** (many policies), **timestamp**, **remove**, **catalog** ops, **sealing** / AppX constraints, response files, broad switch surface ([`psign-cli-matrix.json`](psign-cli-matrix.json)).
Expand Down Expand Up @@ -163,7 +196,7 @@ Portable support is intentionally split by lifecycle stage. This keeps Linux/mac
| Lifecycle stage | `psign-tool --mode portable ...` | `psign-tool portable ...` | Support level |
|-----------------|-----------------------------------|----------------------------|---------------|
| Digest computation | Routed through `verify` only when it can infer a supported subject format | `pe-digest`, `cab-digest`, and format-specific `verify-*` commands | Supported for PE/WinMD, CAB, MSI/MSP, WIM/ESD, cleartext MSIX/AppX, catalogs, and scripts |
| PKCS#7 inspection / extraction | `inspect-signature` routes to `inspect-authenticode` | `inspect-authenticode`, `extract-pe-pkcs7`, `extract-cab-pkcs7`, `extract-msi-pkcs7`, `list-pe-pkcs7` | Supported diagnostics; no trust decision by itself |
| PKCS#7 inspection / extraction | `inspect-signature` routes to `inspect-authenticode` | `inspect-authenticode`, `inspect-pkcs7`, `extract-pkcx-pkcs7`, `extract-pe-pkcs7`, `extract-cab-pkcs7`, `extract-msi-pkcs7`, `list-pe-pkcs7` | Supported diagnostics; no trust decision by itself |
| Explicit-anchor trust verification | `verify` routes only when portable trust inputs are present and the inferred format has a trust command | `trust-verify-pe`, `trust-verify-cab`, `trust-verify-msi`, `trust-verify-esd`, `trust-verify-catalog`, `trust-verify-detached` | Supported with explicit anchors and bounded online AIA/OCSP/CRL; not OS store policy |
| Remote hash/signing | PE Key Vault signing through top-level `sign`; other remote helpers are not routed | `sign-pe --azure-key-vault-*`, `artifact-signing-submit`, `azure-key-vault-sign-digest`, signer prehash commands | PE Key Vault signing embeds Authenticode; other remote helpers are digest-in/signature-out only |
| Local-key signing | Top-level `sign` returns an explicit portable-not-implemented error | `sign-pe`, `sign-cab`, `sign-msi`, `sign-catalog`, `rdp` | Supported for PE, unsigned single-volume CAB, MSI/MSP, generic catalogs, and RDP local RSA signing; other Authenticode SIP subjects remain backlog |
Expand Down
Loading