From 3ec2aa24e81de8d9a0329de13a2438c234d6a538 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andr=C3=A9=20Moreau?= Date: Wed, 13 May 2026 13:00:01 -0400 Subject: [PATCH 1/2] Unify psign tool entry point Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .cargo/config.toml | 2 +- .github/workflows/parity-extensions.yml | 32 +- .github/workflows/rust-sip-parity.yml | 4 +- AGENTS.md | 16 +- Cargo.lock | 1 + Cargo.toml | 15 +- README.md | 40 +-- crates/psign-azure-kv-rest/src/lib.rs | 2 +- crates/psign-digest-cli/Cargo.toml | 6 +- crates/psign-digest-cli/src/lib.rs | 6 + crates/psign-digest-cli/src/main.rs | 26 +- .../psign-digest-cli/tests/cli_pe_digest.rs | 2 +- crates/psign-sip-digest/src/timestamp.rs | 2 +- docs/authenticode-trust-stack.md | 2 +- docs/authroot-linux-verify.md | 10 +- docs/ci-parity.md | 26 +- docs/cli-parity-backlog.md | 2 +- docs/gap-analysis-signing-platforms.md | 36 +-- docs/linux-signing-pipelines.md | 28 +- docs/migration-artifact-signing.md | 76 ++--- docs/migration-azuresigntool.md | 28 +- docs/parity-matrix.md | 26 +- docs/plan-openauthenticode-parity.md | 10 +- docs/psa-interoperability.md | 10 +- docs/psign-cli-matrix.json | 28 +- docs/psign-cli-matrix.md | 4 +- docs/roadmap-authenticode-linux.md | 24 +- docs/rust-sip-architecture.md | 4 +- docs/rust-sip-gaps.md | 10 +- docs/rust-sip-spec-refs.md | 2 +- .../ci/bootstrap-devolutions-authenticode.ps1 | 18 +- scripts/ci/build-catalog-pe-pkcs7-fixture.ps1 | 4 +- scripts/ci/build-signed-cab-fixture.ps1 | 10 +- scripts/ci/pack-minimal-winmd.ps1 | 2 +- scripts/ci/prepare-parity-fixtures.ps1 | 12 +- scripts/ci/run-exhaustive-parity-ci.ps1 | 28 +- scripts/linux-portable-validation.sh | 8 +- scripts/msix-parity-sign.ps1 | 30 +- scripts/run-parity-diff.ps1 | 292 +++++++++--------- scripts/rust-sip-parity-pe.ps1 | 26 +- scripts/sip-format-smoke.ps1 | 46 +-- src/bin/azure_sign_tool_compat.rs | 4 +- src/bin/psign_tool_windows.rs | 3 + src/cli.rs | 35 ++- src/lib.rs | 272 ++++++++++++++-- src/main.rs | 14 +- src/native_argv.rs | 10 +- src/response_argv.rs | 42 +-- src/win/code_sign_format.rs | 2 +- src/win/sign.rs | 18 +- src/win/sign_core.rs | 2 +- tests/cli_verify_filters.rs | 154 ++++----- tests/corpus_sign_verify.rs | 38 +-- tests/cross_cli_windows.rs | 32 +- tests/fixtures/README.md | 22 +- .../catalog-authenticode-upstream/README.md | 4 +- tests/fixtures/corpus.json | 8 +- tests/parity_signtool.rs | 256 ++++++++------- tests/sip_rust_pe.rs | 8 +- 59 files changed, 1029 insertions(+), 851 deletions(-) create mode 100644 crates/psign-digest-cli/src/lib.rs create mode 100644 src/bin/psign_tool_windows.rs diff --git a/.cargo/config.toml b/.cargo/config.toml index 32a40bb..a835feb 100644 --- a/.cargo/config.toml +++ b/.cargo/config.toml @@ -1,6 +1,6 @@ [alias] # Main Windows CLI (`default-members` omit root crate — always pass `-p psign`). -windows-bin = "build -p psign --bin psign-tool-windows --locked" +windows-bin = "build -p psign --bin psign-tool --locked" # Portable Authenticode digest stack (no Win32). See docs/roadmap-authenticode-linux.md. digest-check = "check -p psign-sip-digest -p psign-digest-cli -p psign-authenticode-trust -p psign-codesigning-rest -p psign-azure-kv-rest --locked" # `psign-digest-cli` has integration tests only — keep it last so `--lib` does not silence them. diff --git a/.github/workflows/parity-extensions.yml b/.github/workflows/parity-extensions.yml index 746c7bb..e067a57 100644 --- a/.github/workflows/parity-extensions.yml +++ b/.github/workflows/parity-extensions.yml @@ -10,7 +10,7 @@ on: type: boolean default: false run_catalog_verify: - description: Run Rust catalog verify against SIGNTOOL_RS_CATALOG_* secrets + description: Run Rust catalog verify against PSIGN_CATALOG_* secrets type: boolean default: false @@ -21,23 +21,23 @@ jobs: steps: - uses: actions/checkout@v6 - uses: dtolnay/rust-toolchain@stable - - run: cargo build -p psign --bin psign-tool-windows + - run: cargo build -p psign --bin psign-tool - name: Bootstrap Devolutions test PKI + pack minimal MSIX shell: pwsh env: - SIGNTOOL_RS_MSIX_DLIB: ${{ secrets.SIGNTOOL_RS_MSIX_DLIB }} - SIGNTOOL_RS_MSIX_DMDF: ${{ secrets.SIGNTOOL_RS_MSIX_DMDF }} + PSIGN_MSIX_DLIB: ${{ secrets.PSIGN_MSIX_DLIB }} + PSIGN_MSIX_DMDF: ${{ secrets.PSIGN_MSIX_DMDF }} run: | - if (-not $env:SIGNTOOL_RS_MSIX_DLIB -or -not $env:SIGNTOOL_RS_MSIX_DMDF) { - throw "Set repository secrets SIGNTOOL_RS_MSIX_DLIB and SIGNTOOL_RS_MSIX_DMDF." + if (-not $env:PSIGN_MSIX_DLIB -or -not $env:PSIGN_MSIX_DMDF) { + throw "Set repository secrets PSIGN_MSIX_DLIB and PSIGN_MSIX_DMDF." } ./scripts/ci/bootstrap-devolutions-authenticode.ps1 -EmitGithubEnv $ws = "${{ github.workspace }}" - $exe = Join-Path $ws "target\debug\psign-tool-windows.exe" + $exe = Join-Path $ws "target\debug\psign-tool.exe" $rt = if ($env:RUNNER_TEMP) { $env:RUNNER_TEMP } else { $env:TEMP } $msix = Join-Path $rt "psign_extension_decoupled.msix" ./scripts/ci/pack-minimal-msix.ps1 -WorkspaceRoot $ws -PeAsExecutable $exe -OutputMsix $msix - $env:SIGNTOOL_RS_MSIX_UNSIGNED_FIXTURE = $msix + $env:PSIGN_MSIX_UNSIGNED_FIXTURE = $msix ./scripts/msix-parity-sign.ps1 -UseDecoupledDigest -FailOnSemantic catalog-verify: @@ -46,18 +46,18 @@ jobs: steps: - uses: actions/checkout@v6 - uses: dtolnay/rust-toolchain@stable - - run: cargo build -p psign --bin psign-tool-windows + - run: cargo build -p psign --bin psign-tool - name: Catalog verify (Rust) shell: pwsh env: - SIGNTOOL_RS_CATALOG_TARGET: ${{ secrets.SIGNTOOL_RS_CATALOG_TARGET }} - SIGNTOOL_RS_CATALOG_FILE: ${{ secrets.SIGNTOOL_RS_CATALOG_FILE }} + PSIGN_CATALOG_TARGET: ${{ secrets.PSIGN_CATALOG_TARGET }} + PSIGN_CATALOG_FILE: ${{ secrets.PSIGN_CATALOG_FILE }} run: | - if (-not $env:SIGNTOOL_RS_CATALOG_TARGET -or -not $env:SIGNTOOL_RS_CATALOG_FILE) { - throw "Set repository secrets SIGNTOOL_RS_CATALOG_TARGET and SIGNTOOL_RS_CATALOG_FILE." + if (-not $env:PSIGN_CATALOG_TARGET -or -not $env:PSIGN_CATALOG_FILE) { + throw "Set repository secrets PSIGN_CATALOG_TARGET and PSIGN_CATALOG_FILE." } - $rust = Join-Path "${{ github.workspace }}" "target\debug\psign-tool-windows.exe" - & $rust verify $env:SIGNTOOL_RS_CATALOG_TARGET --catalog $env:SIGNTOOL_RS_CATALOG_FILE + $rust = Join-Path "${{ github.workspace }}" "target\debug\psign-tool.exe" + & $rust verify $env:PSIGN_CATALOG_TARGET --catalog $env:PSIGN_CATALOG_FILE if ($LASTEXITCODE -ne 0) { throw "Catalog verify failed with exit $LASTEXITCODE" } - & $rust verify $env:SIGNTOOL_RS_CATALOG_TARGET --catalog $env:SIGNTOOL_RS_CATALOG_FILE --os-version-check "386:10.0.26100.0" + & $rust verify $env:PSIGN_CATALOG_TARGET --catalog $env:PSIGN_CATALOG_FILE --os-version-check "386:10.0.26100.0" if ($LASTEXITCODE -ne 0) { throw "Catalog verify with --os-version-check failed with exit $LASTEXITCODE" } diff --git a/.github/workflows/rust-sip-parity.yml b/.github/workflows/rust-sip-parity.yml index 66370ff..22deaaf 100644 --- a/.github/workflows/rust-sip-parity.yml +++ b/.github/workflows/rust-sip-parity.yml @@ -1,7 +1,7 @@ name: rust-sip-parity # Portable SIP digest parity: Windows fixture/script smoke plus Linux gates for CMS §5.4 RS256 -# prehash ↔ PKCS#1 signature (Azure KV `keys/sign` contract toward psign-tool-portable signing). +# prehash ↔ PKCS#1 signature (Azure KV `keys/sign` contract toward psign-tool portable signing). on: push: branches: [master, main] @@ -60,7 +60,7 @@ jobs: - name: RFC3161 TimeStampReq/Resp + timestamp policy (library + CLI) run: cargo test -p psign-sip-digest --lib timestamp:: --locked && cargo test -p psign-authenticode-trust --lib tiny32_upstream --locked && cargo test -p psign-authenticode-trust --lib tiny64_upstream_pe_pkcs7_pkcs9_signing_time_extracts --locked && cargo test -p psign-authenticode-trust --lib tiny64_upstream_bare_signed --locked && cargo test -p psign-authenticode-trust --lib nested_tstinfo --locked && cargo test -p psign-authenticode-trust --lib time_rejects --locked && cargo test -p psign-authenticode-trust --lib resolve_verification_ --locked && cargo test -p psign-digest-cli --locked trust_verify_pe_ok_require_valid_timestamp_pkcs9_signing_time_on_tiny32 && cargo test -p psign-digest-cli --locked trust_verify_pe_ok_require_valid_timestamp_pkcs9_signing_time_on_tiny64 && cargo test -p psign-digest-cli --locked portable_rfc3161_timestamp - - name: Build psign-tool-portable (timestamp-http feature, compile-only) + - name: Build psign-tool portable (timestamp-http feature, compile-only) run: cargo build -p psign-digest-cli --features timestamp-http --locked - name: Artifact Signing REST (library + mock :sign LRO) diff --git a/AGENTS.md b/AGENTS.md index 3ba95dc..1cb2c4e 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -6,20 +6,20 @@ This repository is a **Rust port** of the Windows SDK **`signtool.exe`** (Authen | Area | Path | Notes | |------|------|--------| -| Root package (Windows CLI + lib) | `Cargo.toml` (package **`psign`**) | **`windows`** crate feature deps under **`cfg(windows)`**; non-Windows builds use a stub **`main`**. | +| Root package (unified CLI + lib) | `Cargo.toml` (package **`psign`**) | **`psign-tool`** dispatches to Win32 code on Windows or portable Rust paths via `--mode`; **`windows`** crate feature deps stay under **`cfg(windows)`**. | | Portable digest library | `crates/psign-sip-digest` | No **`windows`** dependency; Linux-safe unit tests. | -| Portable Authenticode trust | `crates/psign-authenticode-trust` | Anchors + picky chain; **`psign-tool-portable`** **`trust-verify-pe`**, **`trust-verify-cab`**, **`trust-verify-catalog`**, **`trust-verify-detached`** — no OS trust store. | -| Portable CLI | `crates/psign-digest-cli` | Binary **`psign-tool-portable`**. | +| Portable Authenticode trust | `crates/psign-authenticode-trust` | Anchors + picky chain; **`psign-tool portable`** **`trust-verify-pe`**, **`trust-verify-cab`**, **`trust-verify-catalog`**, **`trust-verify-detached`** — no OS trust store. | +| Portable CLI runner | `crates/psign-digest-cli` | Callable by **`psign-tool portable ...`**; compatibility binary **`psign-tool-portable`** remains during transition. | | Win32 implementation | `src/win/` | Verify, sign, timestamp, catalog, detached PKCS#7, etc. | -| argv / response files | `src/native_argv.rs`, `src/response_argv.rs` | Shared with stub builds for **`cargo check`** on Unix. | +| argv / response files | `src/native_argv.rs`, `src/response_argv.rs` | Shared by unified CLI and portable-mode builds. | **Important:** **`default-members`** are the three crates under **`crates/`** (digest, digest CLI, authenticode-trust). A bare **`cargo build`** or **`cargo test`** at the repo root does **not** build the **`psign`** binary unless you use **`--workspace`** or **`-p psign`**. ## Cargo aliases (`.cargo/config.toml`) -- **`cargo windows-bin`** — build **`psign-tool-windows`** exe (**`-p psign --bin psign-tool-windows`**). +- **`cargo windows-bin`** — build **`psign-tool`** exe (**`-p psign --bin psign-tool`**). - **`cargo digest-check`** / **`cargo digest-test`** — portable digest + trust crates (see **`.cargo/config.toml`**). -- **`cargo unix-lib-check`** — **`psign`** library on non-Windows (stub-friendly). +- **`cargo unix-lib-check`** — **`psign`** library on non-Windows (portable-mode friendly). - **`cargo depgraph`** — **`psign-depgraph`** binary (**needs `-p`** because of **`default-members`**). ## Commands agents should run @@ -44,8 +44,8 @@ On Linux/macOS, match **`ci-unix`**: fmt check, metadata **`--locked`**, clippy | **`docs/rust-sip-architecture.md`** | Rust SIP digest add-ons vs OS SIP. | | **`docs/rust-sip-gaps.md`** | Known limitations (MSIX sign gap, `/ph`, PKCS#7 encode, VBA, encrypted MSIX, …). | | **`docs/rust-sip-spec-refs.md`** | Spec links + PE page-hash / **`SignerSignEx3`** notes. | -| **`docs/ci-parity.md`** | CI steps, **`SIGNTOOL_RS_*`** env vars, parity gates. | -| **`docs/roadmap-authenticode-linux.md`** | Unix/portable subset and **`psign-tool-portable`**. | +| **`docs/ci-parity.md`** | CI steps, **`PSIGN_*`** env vars, parity gates. | +| **`docs/roadmap-authenticode-linux.md`** | Unix/portable subset and **`psign-tool portable`**. | | **`docs/authenticode-trust-stack.md`** | Portable trust crate split (picky vs digest vs CMS). | | **`docs/authroot-linux-verify.md`** | Anchor dir + AuthRoot CAB usage on Linux. | | **`docs/plan-linux-authenticode-trust-verify.md`** | Technical plan (CTL, test matrix, risks). | diff --git a/Cargo.lock b/Cargo.lock index dfe65bd..49a6af0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2012,6 +2012,7 @@ dependencies = [ "psign-authenticode-trust", "psign-azure-kv-rest", "psign-codesigning-rest", + "psign-digest-cli", "psign-sip-digest", "rayon", "reqwest", diff --git a/Cargo.toml b/Cargo.toml index 10a3d02..a5029b6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -26,13 +26,18 @@ description = "Rust port of the Windows SDK signtool.exe (Authenticode sign/veri [features] default = [] ## Azure Key Vault signing (`AuthenticatorDigestSign` callback + REST); enables Azure-shaped CLI flags on `sign`. -azure-kv-sign = ["dep:psign-azure-kv-rest", "dep:reqwest"] +azure-kv-sign = [ + "dep:psign-azure-kv-rest", + "dep:reqwest", + "psign-digest-cli/azure-kv-sign-portable", +] ## Azure Artifact Signing / Trusted Signing **data-plane** hash signing (REST LRO); experimental helper command `artifact-signing-submit`. -artifact-signing-rest = ["dep:psign-codesigning-rest"] +artifact-signing-rest = ["dep:psign-codesigning-rest", "psign-digest-cli/artifact-signing-rest"] [dependencies] psign-sip-digest = { path = "crates/psign-sip-digest" } psign-authenticode-trust = { path = "crates/psign-authenticode-trust" } +psign-digest-cli = { path = "crates/psign-digest-cli" } anyhow = "1" clap = { version = "4", features = ["derive"] } serde = { version = "1", features = ["derive"] } @@ -67,9 +72,13 @@ predicates = "3" psign-authenticode-trust = { path = "crates/psign-authenticode-trust" } [[bin]] -name = "psign-tool-windows" +name = "psign-tool" path = "src/main.rs" +[[bin]] +name = "psign-tool-windows" +path = "src/bin/psign_tool_windows.rs" + [[bin]] name = "psign-azure-sign-tool-compat" path = "src/bin/azure_sign_tool_compat.rs" diff --git a/README.md b/README.md index d8e6b3e..b56840e 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ differential parity tests against the native tool where CI fixtures allow. - `verify`: WinVerifyTrust-backed implementation with policy modes (`default`, `pa`, `pg`). - `sign`: Rust mssign32 core (`SignerSignEx3`) with PFX/system-store cert selection, RFC3161 sign-time timestamping, and decoupled-digest bridge flow (`--dlib` or `--trusted-signing-dlib-root` + `--dmdf`) for MSIX parity and [Azure Artifact Signing / Trusted Signing](docs/migration-artifact-signing.md). -- `inspect-signature`: JSON dump of PKCS#7 signers, timestamp OIDs, and nested signatures (`1.3.6.1.4.1.311.2.4.1`) — same parser as **`psign-tool-portable inspect-authenticode`** ([docs/psa-interoperability.md](docs/psa-interoperability.md)). +- `inspect-signature`: JSON dump of PKCS#7 signers, timestamp OIDs, and nested signatures (`1.3.6.1.4.1.311.2.4.1`) — same parser as **`psign-tool portable inspect-authenticode`** ([docs/psa-interoperability.md](docs/psa-interoperability.md)). - `timestamp`: Rust mssign32 core (`SignerTimeStampEx3`/`SignerTimeStampEx2`) plus AppX restrictions. - `rdp`: Rust port of **`rdpsign.exe`** for `.rdp` files (`SignScope` / `Signature` records, detached PKCS#7 over the secure-settings blob). @@ -24,23 +24,23 @@ differential parity tests against the native tool where CI fixtures allow. cargo build ``` -At the repo root, **`cargo build`** targets **`default-members`** only (**portable digest crates**). On Windows, build the **`psign-tool-windows`** executable with **`cargo windows-bin`** or **`cargo build -p psign --bin psign-tool-windows`** (see [`.cargo/config.toml`](.cargo/config.toml)). Optional Cargo features: **`azure-kv-sign`** (Key Vault digest callback), **`artifact-signing-rest`** (**`artifact-signing-submit`** LRO against **`*.codesigning.azure.net`**). +At the repo root, **`cargo build`** targets **`default-members`** only (**portable digest crates**). On Windows, build the **`psign-tool`** executable with **`cargo windows-bin`** or **`cargo build -p psign --bin psign-tool`** (see [`.cargo/config.toml`](.cargo/config.toml)). Optional Cargo features: **`azure-kv-sign`** (Key Vault digest callback), **`artifact-signing-rest`** (**`artifact-signing-submit`** LRO against **`*.codesigning.azure.net`**). ## Linux / portable digest tooling -The **`psign-tool-windows`** CLI (package **`psign`**) is Windows-only (stub exits on other targets). Cross-platform pieces live in **`psign-sip-digest`** and the **`psign-tool-portable`** binary (`crates/psign-digest-cli`). They exercise the same PE-derived Authenticode digest logic used for **PE and WinMD** (CLI metadata), plus CAB, MSI, ESD/WIM, cleartext MSIX, catalog, scripts, and portable `.rdp` signing—without **`WinVerifyTrust`**. +The canonical **`psign-tool`** CLI (package **`psign`**) supports an optional backend selector: **`--mode auto|windows|portable`**. When omitted, **`auto`** is used; **`PSIGN_TOOL_MODE`** can set the same default for parity automation. Windows mode uses Win32 APIs and registered SIP DLLs. Portable mode and the **`psign-tool portable ...`** namespace use the cross-platform Rust implementations from **`psign-sip-digest`** and **`psign-authenticode-trust`** without **`WinVerifyTrust`**. -**Feature gaps vs native `signtool`, AzureSignTool, and Azure Artifact Signing:** [`docs/gap-analysis-signing-platforms.md`](docs/gap-analysis-signing-platforms.md). **Linux workflows (verify, REST hash sign, hybrid embed):** [`docs/linux-signing-pipelines.md`](docs/linux-signing-pipelines.md). For Key Vault **`RS256`** over CMS authenticated attributes (not the PE image hash), use **`psign-tool-portable pe-signer-rs256-prehash`** — see [`docs/migration-azuresigntool.md`](docs/migration-azuresigntool.md). +**Feature gaps vs native `signtool`, AzureSignTool, and Azure Artifact Signing:** [`docs/gap-analysis-signing-platforms.md`](docs/gap-analysis-signing-platforms.md). **Linux workflows (verify, REST hash sign, hybrid embed):** [`docs/linux-signing-pipelines.md`](docs/linux-signing-pipelines.md). For Key Vault **`RS256`** over CMS authenticated attributes (not the PE image hash), use **`psign-tool portable pe-signer-rs256-prehash`** — see [`docs/migration-azuresigntool.md`](docs/migration-azuresigntool.md). From the repo root (see [`docs/roadmap-authenticode-linux.md`](docs/roadmap-authenticode-linux.md)): ```sh -cargo install --path crates/psign-digest-cli --locked # installs `psign-tool-portable` +cargo build -p psign --bin psign-tool --locked # Portable RDP signing: -# psign-tool-portable rdp --cert cert.der --key key.pk8 file.rdp +# psign-tool portable rdp --cert cert.der --key key.pk8 file.rdp # Optional portable REST helpers (Linux/macOS): -# cargo install --path crates/psign-digest-cli --locked --features artifact-signing-rest -# cargo install --path crates/psign-digest-cli --locked --features azure-kv-sign-portable +# cargo build -p psign --bin psign-tool --locked --features artifact-signing-rest +# cargo build -p psign --bin psign-tool --locked --features azure-kv-sign cargo digest-test # alias: sip-digest + authenticode-trust + codesigning-rest lib tests + digest-cli integration tests cargo digest-check # alias: `cargo check` on portable workspace crates (includes `psign-codesigning-rest`) ``` @@ -80,7 +80,7 @@ cargo test --test parity_signtool -- --ignored --nocapture ./scripts/run-parity-diff.ps1 -FailOnSemantic ``` -`-FailOnSemantic` requires `SIGNTOOL_RS_UNSIGNED_FIXTURE` and `SIGNTOOL_RS_TEST_PFX`. Add `-FailOnSemanticExhaustive` when timestamp, MSIX package, and detached PKCS#7 env vars are also set (see [`docs/ci-parity.md`](docs/ci-parity.md)). +`-FailOnSemantic` requires `PSIGN_UNSIGNED_FIXTURE` and `PSIGN_TEST_PFX`. Add `-FailOnSemanticExhaustive` when timestamp, MSIX package, and detached PKCS#7 env vars are also set (see [`docs/ci-parity.md`](docs/ci-parity.md)). ## CI parity (GitHub Actions) @@ -89,36 +89,36 @@ The **`windows`** workflow builds the repo, bootstraps the public Devolutions te Local mirror of the CI orchestrator: ```powershell -cargo build -p psign --bin psign-tool-windows +cargo build -p psign --bin psign-tool ./scripts/ci/run-exhaustive-parity-ci.ps1 ``` ## MSIX parity signing script -Use the dedicated local parity runner to sign the same unsigned MSIX with native `signtool.exe` and `psign-tool-windows`, then compare verification outcomes: +Use the dedicated local parity runner to sign the same unsigned MSIX with native `signtool.exe` and `psign-tool`, then compare verification outcomes: ```powershell -$env:SIGNTOOL_RS_MSIX_UNSIGNED_FIXTURE="D:\path\unsigned.msix" -$env:SIGNTOOL_RS_MSIX_TEST_PFX="D:\path\authenticode-test-cert.pfx" -$env:SIGNTOOL_RS_MSIX_TEST_PFX_PASSWORD="CodeSign123!" -$env:SIGNTOOL_RS_MSIX_TIMESTAMP_URL="http://timestamp.digicert.com" +$env:PSIGN_MSIX_UNSIGNED_FIXTURE="D:\path\unsigned.msix" +$env:PSIGN_MSIX_TEST_PFX="D:\path\authenticode-test-cert.pfx" +$env:PSIGN_MSIX_TEST_PFX_PASSWORD="CodeSign123!" +$env:PSIGN_MSIX_TIMESTAMP_URL="http://timestamp.digicert.com" ./scripts/msix-parity-sign.ps1 -FailOnSemantic ``` If you already imported the Devolutions test cert into `CurrentUser\\My`, you can use thumbprint mode instead of a PFX: ```powershell -$env:SIGNTOOL_RS_MSIX_UNSIGNED_FIXTURE="D:\path\unsigned.msix" -$env:SIGNTOOL_RS_MSIX_TEST_CERT_SHA1="A9FDF3593E91689CC93B1CEBED5E8FFC1F6FEE38" -$env:SIGNTOOL_RS_MSIX_TIMESTAMP_URL="http://timestamp.digicert.com" +$env:PSIGN_MSIX_UNSIGNED_FIXTURE="D:\path\unsigned.msix" +$env:PSIGN_MSIX_TEST_CERT_SHA1="A9FDF3593E91689CC93B1CEBED5E8FFC1F6FEE38" +$env:PSIGN_MSIX_TIMESTAMP_URL="http://timestamp.digicert.com" ./scripts/msix-parity-sign.ps1 -FailOnSemantic ``` Optional decoupled digest parity: ```powershell -$env:SIGNTOOL_RS_MSIX_DLIB="D:\path\provider.dll" -$env:SIGNTOOL_RS_MSIX_DMDF="D:\path\metadata.json" +$env:PSIGN_MSIX_DLIB="D:\path\provider.dll" +$env:PSIGN_MSIX_DMDF="D:\path\metadata.json" ./scripts/msix-parity-sign.ps1 -UseDecoupledDigest -FailOnSemantic ``` diff --git a/crates/psign-azure-kv-rest/src/lib.rs b/crates/psign-azure-kv-rest/src/lib.rs index 302e1ee..54c987f 100644 --- a/crates/psign-azure-kv-rest/src/lib.rs +++ b/crates/psign-azure-kv-rest/src/lib.rs @@ -26,7 +26,7 @@ pub enum KvHashAlg { Sha512, } -/// Authentication inputs (same modes as **`psign-tool-windows`** Azure KV signing). +/// Authentication inputs (same modes as **`psign-tool`** Azure KV signing). #[derive(Debug, Clone, Copy)] pub struct KvAuthParams<'a> { pub access_token: Option<&'a str>, diff --git a/crates/psign-digest-cli/Cargo.toml b/crates/psign-digest-cli/Cargo.toml index e58e155..6d63aa4 100644 --- a/crates/psign-digest-cli/Cargo.toml +++ b/crates/psign-digest-cli/Cargo.toml @@ -6,13 +6,17 @@ description = "Linux/macOS-friendly CLI over portable Authenticode SIP digests ( [features] default = [] -## Azure Code Signing `:sign` LRO on Linux/macOS (same API as `psign-tool-windows artifact-signing-submit`). +## Azure Code Signing `:sign` LRO on Linux/macOS (same API as `psign-tool artifact-signing-submit`). artifact-signing-rest = ["dep:psign-codesigning-rest"] ## Azure Key Vault `keys/sign` on Linux/macOS (digest file → signature bytes / base64); embedding still requires Windows. azure-kv-sign-portable = ["dep:psign-azure-kv-rest", "dep:base64", "dep:reqwest"] ## RFC 3161 TSA HTTP POST (`rfc3161-timestamp-http-post`) using Rustls (same optional `reqwest` crate as KV portable). timestamp-http = ["dep:reqwest"] +[lib] +name = "psign_digest_cli" +path = "src/lib.rs" + [[bin]] name = "psign-tool-portable" path = "src/main.rs" diff --git a/crates/psign-digest-cli/src/lib.rs b/crates/psign-digest-cli/src/lib.rs new file mode 100644 index 0000000..e28243b --- /dev/null +++ b/crates/psign-digest-cli/src/lib.rs @@ -0,0 +1,6 @@ +#[allow(dead_code)] +mod imp { + include!("main.rs"); +} + +pub use imp::run_from; diff --git a/crates/psign-digest-cli/src/main.rs b/crates/psign-digest-cli/src/main.rs index a1aaf07..981e0de 100644 --- a/crates/psign-digest-cli/src/main.rs +++ b/crates/psign-digest-cli/src/main.rs @@ -1,7 +1,7 @@ -//! Cross-platform helper over [`psign_sip_digest`] — **no WinVerifyTrust**. -//! -//! Use this on Linux/macOS to compute PE image digests or to check PKCS#7 indirect-data consistency -//! for formats implemented in `psign-sip-digest`. This does **not** replace full `psign` verify. +// Cross-platform helper over `psign_sip_digest` — no WinVerifyTrust. +// +// Use this on Linux/macOS to compute PE image digests or to check PKCS#7 indirect-data consistency +// for formats implemented in `psign-sip-digest`. This does not replace full `psign` verify. use anyhow::{Context, Result, anyhow}; #[cfg(feature = "azure-kv-sign-portable")] @@ -45,11 +45,11 @@ use psign_sip_digest::timestamp::{ use psign_sip_digest::verify_pe; use psign_sip_digest::verify_script_digest_consistency; use serde::Deserialize; -use std::ffi::OsStr; +use std::ffi::{OsStr, OsString}; use std::path::{Path, PathBuf}; #[derive(Parser)] -#[command(name = "psign-tool-portable")] +#[command(name = "psign-tool")] #[command(version, about = "Portable Authenticode SIP digest utilities (no Windows CryptoAPI)", long_about = None)] struct Cli { #[command(subcommand)] @@ -589,13 +589,13 @@ enum Command { #[arg(long, value_name = "PATH")] path: Option, }, - /// Azure Code Signing **`…:sign`** LRO (same REST contract as **`psign-tool-windows artifact-signing-submit`**). Requires **`--features artifact-signing-rest`** at build time. + /// Azure Code Signing **`…:sign`** LRO (same REST contract as **`psign-tool artifact-signing-submit`**). Requires **`--features artifact-signing-rest`** at build time. #[cfg(feature = "artifact-signing-rest")] ArtifactSigningSubmit { #[command(flatten)] args: ArtifactSigningSubmitPortableArgs, }, - /// Azure Key Vault **`keys/sign`** over a **precomputed digest file** (RSA PKCS#1 or ECDSA). Requires **`--features azure-kv-sign-portable`**. Does **not** embed Authenticode — use **`psign-tool-windows`** for that. + /// Azure Key Vault **`keys/sign`** over a **precomputed digest file** (RSA PKCS#1 or ECDSA). Requires **`--features azure-kv-sign-portable`**. Does **not** embed Authenticode — use **`psign-tool`** for that. #[cfg(feature = "azure-kv-sign-portable")] AzureKeyVaultSignDigest { #[command(flatten)] @@ -1185,7 +1185,15 @@ fn main() { } fn run() -> Result<()> { - let cli = Cli::parse(); + run_from(std::env::args_os()) +} + +pub fn run_from(args: I) -> Result<()> +where + I: IntoIterator, + T: Into + Clone, +{ + let cli = Cli::parse_from(args); match cli.command { Command::PeDigest { path, diff --git a/crates/psign-digest-cli/tests/cli_pe_digest.rs b/crates/psign-digest-cli/tests/cli_pe_digest.rs index caed38f..760b750 100644 --- a/crates/psign-digest-cli/tests/cli_pe_digest.rs +++ b/crates/psign-digest-cli/tests/cli_pe_digest.rs @@ -33,7 +33,7 @@ fn binary_reports_name_and_version_flag() { cmd.arg("--version"); cmd.assert() .success() - .stdout(predicate::str::contains("psign-tool-portable")) + .stdout(predicate::str::contains("psign-tool")) .stdout(predicate::str::contains(env!("CARGO_PKG_VERSION"))); } diff --git a/crates/psign-sip-digest/src/timestamp.rs b/crates/psign-sip-digest/src/timestamp.rs index ef9e5a6..54a7a4d 100644 --- a/crates/psign-sip-digest/src/timestamp.rs +++ b/crates/psign-sip-digest/src/timestamp.rs @@ -5,7 +5,7 @@ //! when **`--prefer-timestamp-signing-time`** is set (**`psign-authenticode-trust`**). This module //! encodes minimal **§2.4.1** requests (version 1, **`messageImprint`**, optional **`nonce`** / //! **`certReq`**) and parses **§2.4.2** responses far enough to read **PKIStatus** and locate the -//! optional **`timeStampToken`** TLV. **HTTP transport** is implemented in **`psign-tool-portable`** +//! optional **`timeStampToken`** TLV. **HTTP transport** is implemented in **`psign-tool portable`** //! behind **`--features timestamp-http`** (**`rfc3161-timestamp-http-post`**). Optional **`failInfo`** //! (**`PKIFailureInfo`**) **`BIT STRING`** values can be decoded to RFC 2510 Appendix A bit names //! (**`badAlg`** … **`badPOP`**) for logging; **CMS signature / `MessageImprint` verification** remain out of scope. diff --git a/docs/authenticode-trust-stack.md b/docs/authenticode-trust-stack.md index 59db9c1..3ecdc21 100644 --- a/docs/authenticode-trust-stack.md +++ b/docs/authenticode-trust-stack.md @@ -11,7 +11,7 @@ This describes how **`crates/psign-authenticode-trust`** composes crates for **L | CMS Authenticode rules + X.509 chain verification | **`picky`** (`AuthenticodeSignature`, `authenticode_verifier`, `Cert::verifier`) | Validate **`messageDigest`** vs provided digest, signature over authenticated attributes, TBSCertificate signatures along **`issuer_chain`**, Basic Constraints / dates / EKU policy hooks. | | Trust anchors | This crate (**`anchor`**, **`authroot_cab`**, **`authroot_ctl`**) | Phase A: load **`*.crt`/`*.cer`/`*.pem`** from **`--anchor-dir`**. Phase B: CAB **`*.stl`** → PKCS#7 **`SignedData`** **`eContent`** CTL parse for **SHA-1 subject identifiers** plus PKCS#7-embedded certs. | | Policy knobs | **`policy::AuthenticodeTrustPolicy`** | Default **strict** code-signing EKU; CLI **`allow-loose-signing-cert`**, **`--prefer-timestamp-signing-time`** / **`--require-valid-timestamp`** (see [**Verification instant / timestamps**](#verification-instant--timestamps)), **`--as-of YYYY-MM-DD`** for **`exact_date`**. | -| Portable CLI | **`psign-tool-portable`** | **`trust-verify-pe`**, **`trust-verify-cab`**, **`trust-verify-catalog`**, **`trust-verify-detached`** share anchor flags; detached uses [`pkcs7_wire::normalize_pkcs7_der_for_authenticode`](../crates/psign-sip-digest/src/pkcs7_wire.rs). **`inspect-authenticode`** emits JSON for PKCS#7 signers, timestamp-related OIDs, and nested signatures (**`1.3.6.1.4.1.311.2.4.1`**). | +| Portable CLI | **`psign-tool portable`** | **`trust-verify-pe`**, **`trust-verify-cab`**, **`trust-verify-catalog`**, **`trust-verify-detached`** share anchor flags; detached uses [`pkcs7_wire::normalize_pkcs7_der_for_authenticode`](../crates/psign-sip-digest/src/pkcs7_wire.rs). **`inspect-authenticode`** emits JSON for PKCS#7 signers, timestamp-related OIDs, and nested signatures (**`1.3.6.1.4.1.311.2.4.1`**). | | CMS inspection (no trust decision) | This crate **`inspect`** | Uses **`cms`** **`SignedData`** + **`authenticode`** digest probe; complements picky **`trust_*`** paths. See [**psa-interoperability.md**](psa-interoperability.md). | ## Verification order (per PKCS#7 blob) diff --git a/docs/authroot-linux-verify.md b/docs/authroot-linux-verify.md index b5542c3..48f46f0 100644 --- a/docs/authroot-linux-verify.md +++ b/docs/authroot-linux-verify.md @@ -1,6 +1,6 @@ # AuthRoot-style anchors on Linux -Windows resolves Authenticode chains against machine/user certificate stores plus **Microsoft Authenticode roots**. On Linux, **`psign-tool-portable trust-verify-pe`** requires you to supply **explicit roots** (and optionally intermediates embedded in the PE PKCS#7). +Windows resolves Authenticode chains against machine/user certificate stores plus **Microsoft Authenticode roots**. On Linux, **`psign-tool portable trust-verify-pe`** requires you to supply **explicit roots** (and optionally intermediates embedded in the PE PKCS#7). ## Phase A — anchor directory (recommended first ship) @@ -30,7 +30,7 @@ Pass **`--authroot-cab /path/to/authrootstl.cab`** on any **`trust-verify-*`** s ## Example ```bash -psign-tool-portable trust-verify-pe \ +psign-tool portable trust-verify-pe \ --anchor-dir ./my-trusted-roots \ ./signed.exe ``` @@ -38,7 +38,7 @@ psign-tool-portable trust-verify-pe \ With Microsoft root harvest from CAB: ```bash -psign-tool-portable trust-verify-pe \ +psign-tool portable trust-verify-pe \ --authroot-cab ./authrootstl.cab \ --anchor-dir ./extra-enterprise-roots \ --verbose-chain \ @@ -48,13 +48,13 @@ psign-tool-portable trust-verify-pe \ Strict **code signing** EKU is default; for diagnostics only: ```bash -psign-tool-portable trust-verify-pe --allow-loose-signing-cert ... +psign-tool portable trust-verify-pe --allow-loose-signing-cert ... ``` Expired corpora / reproducible CI: pin verification instant: ```bash -psign-tool-portable trust-verify-pe --anchor-dir ./roots --as-of 2023-07-01 ./fixture.exe +psign-tool portable trust-verify-pe --anchor-dir ./roots --as-of 2023-07-01 ./fixture.exe ``` ## Related docs diff --git a/docs/ci-parity.md b/docs/ci-parity.md index 5c49284..74d156e 100644 --- a/docs/ci-parity.md +++ b/docs/ci-parity.md @@ -1,6 +1,6 @@ # CI parity tiers -GitHub Actions exercises differential parity between native `signtool.exe` and **`psign-tool-windows`** (crate **`psign`**). Certificate material comes from the public **Devolutions Authenticode** test PKI ([`devolutions-authenticode`](https://github.com/Devolutions/devolutions-authenticode)): `authenticode-test-ca.crt` and `authenticode-test-cert.pfx` with password `CodeSign123!` (test-only; do not use in production). +GitHub Actions exercises differential parity between native `signtool.exe` and **`psign-tool`** (crate **`psign`**). Certificate material comes from the public **Devolutions Authenticode** test PKI ([`devolutions-authenticode`](https://github.com/Devolutions/devolutions-authenticode)): `authenticode-test-ca.crt` and `authenticode-test-cert.pfx` with password `CodeSign123!` (test-only; do not use in production). On **Linux**, the default portable gate is **`ci-unix.yml`**; locally you can run **`bash scripts/linux-portable-validation.sh`** from the repo root (same steps as that workflow’s Rust checks). @@ -8,10 +8,10 @@ On **Linux**, the default portable gate is **`ci-unix.yml`**; locally you can ru Runs on every push, PR, and daily schedule. -1. **`scripts/ci/bootstrap-devolutions-authenticode.ps1`** — Uses vendored public Devolutions test CA + PFX from [`tests/fixtures/devolutions-authenticode/`](../tests/fixtures/devolutions-authenticode/) when present (hash-checked), otherwise downloads the same files from raw URLs pinned to a fixed commit SHA; imports the CA into the machine (or user) trusted root, sets `SIGNTOOL_RS_TEST_PFX*` and timestamp URLs. -2. **`scripts/ci/prepare-parity-fixtures.ps1`** — Native-signs a temp PE (`SIGNTOOL_RS_SIGNED_FIXTURE`) for timestamp scenarios; produces detached PKCS#7 via native `signtool sign /p7 …` (`SIGNTOOL_RS_DETACHED_*`). -3. **`scripts/ci/pack-minimal-msix.ps1`** — Packs [`tests/fixtures/msix-minimal/`](../tests/fixtures/msix-minimal/AppxManifest.xml) + `noop.exe` (copy of the built `psign-tool-windows.exe`) into an unsigned `.msix`. -4. **`scripts/ci/pack-minimal-winmd.ps1`** — Copies the same unsigned `psign-tool-windows.exe` to **`SIGNTOOL_RS_WINMD_UNSIGNED_FIXTURE`** (`.winmd` extension) so **`run-parity-diff`** exercises WinMD SIP scenarios; **`SIGNTOOL_RS_WINMD_TIMESTAMP_URL`** mirrors **`SIGNTOOL_RS_TIMESTAMP_URL`** when set after bootstrap. +1. **`scripts/ci/bootstrap-devolutions-authenticode.ps1`** — Uses vendored public Devolutions test CA + PFX from [`tests/fixtures/devolutions-authenticode/`](../tests/fixtures/devolutions-authenticode/) when present (hash-checked), otherwise downloads the same files from raw URLs pinned to a fixed commit SHA; imports the CA into the machine (or user) trusted root, sets `PSIGN_TEST_PFX*` and timestamp URLs. +2. **`scripts/ci/prepare-parity-fixtures.ps1`** — Native-signs a temp PE (`PSIGN_SIGNED_FIXTURE`) for timestamp scenarios; produces detached PKCS#7 via native `signtool sign /p7 …` (`PSIGN_DETACHED_*`). +3. **`scripts/ci/pack-minimal-msix.ps1`** — Packs [`tests/fixtures/msix-minimal/`](../tests/fixtures/msix-minimal/AppxManifest.xml) + `noop.exe` (copy of the built `psign-tool.exe`) into an unsigned `.msix`. +4. **`scripts/ci/pack-minimal-winmd.ps1`** — Copies the same unsigned `psign-tool.exe` to **`PSIGN_WINMD_UNSIGNED_FIXTURE`** (`.winmd` extension) so **`run-parity-diff`** exercises WinMD SIP scenarios; **`PSIGN_WINMD_TIMESTAMP_URL`** mirrors **`PSIGN_TIMESTAMP_URL`** when set after bootstrap. 5. **`scripts/run-parity-diff.ps1 -FailOnSemantic -FailOnSemanticExhaustive`** — Static CLI matrix, remove scenarios, PE + script description parity, timestamp exits, detached verify, catalog path if env set, MSIX semantic blocks when env present, WinMD scenarios when the WinMD fixture env is set, etc. Exhaustive mode asserts that core PE, timestamp, MSIX package, and detached env vars are all set before running. 6. **`scripts/msix-parity-sign.ps1 -FailOnSemantic`** — Focused MSIX sign/verify report (`parity-output/msix-parity-sign-report.json`). @@ -19,9 +19,9 @@ Artifacts: `parity-output/parity-report.json`, `parity-output/msix-parity-sign-r ## Local `run-parity-diff.ps1` snapshot -Optional parity scenarios are gated on environment variables (`SIGNTOOL_RS_MSIX_*`, `SIGNTOOL_RS_SIGNED_FIXTURE` + timestamp URL, detached PKCS#7 paths, catalog paths, and so on). If only a **subset** is set—for example an MSIX fixture path without the matching PFX and RFC3161 URL—the script still appends those scenarios and you may see `semantic_mismatch` rows that disappear once variables are cleared or the full matrix is provided. +Optional parity scenarios are gated on environment variables (`PSIGN_MSIX_*`, `PSIGN_SIGNED_FIXTURE` + timestamp URL, detached PKCS#7 paths, catalog paths, and so on). If only a **subset** is set—for example an MSIX fixture path without the matching PFX and RFC3161 URL—the script still appends those scenarios and you may see `semantic_mismatch` rows that disappear once variables are cleared or the full matrix is provided. -For a **baseline** report matching the static tier (verify/remove scenarios only, no optional blocks), clear process env vars whose names start with `SIGNTOOL_RS_`, then run `scripts/run-parity-diff.ps1`. Expect **21** scenarios, `missingScenarioCount: 0`, and `semanticMismatchCount: 0` (UTF-16 `@rsp` remains `documented_native_utf16_rsp_gap`, not a semantic failure). +For a **baseline** report matching the static tier (verify/remove scenarios only, no optional blocks), clear process env vars whose names start with `PSIGN_`, then run `scripts/run-parity-diff.ps1`. Expect **21** scenarios, `missingScenarioCount: 0`, and `semanticMismatchCount: 0` (UTF-16 `@rsp` remains `documented_native_utf16_rsp_gap`, not a semantic failure). ## Tier 2 — Extensions (`parity-extensions.yml`) @@ -29,12 +29,12 @@ Manual **`workflow_dispatch`** only. | Input | Requires secrets | Behavior | |-------|------------------|----------| -| **MSIX decoupled** | `SIGNTOOL_RS_MSIX_DLIB`, `SIGNTOOL_RS_MSIX_DMDF` | Repacks minimal MSIX, runs `msix-parity-sign.ps1 -UseDecoupledDigest` (same **`documented_rust_msix_sign_ex3_gap`** vs **`semantic_mismatch`** rules as embedded MSIX when sign exits differ). If you run **`run-parity-diff.ps1`** with those env vars set, **`artifact_msix_decoupled_semantic`** uses the same classification. | -| **Catalog verify** | `SIGNTOOL_RS_CATALOG_TARGET`, `SIGNTOOL_RS_CATALOG_FILE` | Runs `psign-tool-windows verify --catalog `, then the same with `--os-version-check 386:10.0.26100.0`. Optional exhaustive parity records both as `artifact_catalog_*`. | -| **Artifact Signing decoupled** | `SIGNTOOL_RS_ARTIFACT_SIGNING_*` (see below) | Optional local-only parity: run `cargo test -p psign --test parity_signtool artifact_signing_decoupled_pe_executes -- --ignored --nocapture` when fixtures + Microsoft dlib/metadata are available. Not wired into GitHub Actions by default. | -| **Artifact Signing REST submit** | `SIGNTOOL_RS_ARTIFACT_SIGNING_REST_*` | Optional: build **`psign-tool-windows`** with **`--features artifact-signing-rest`**, then run **`artifact_signing_rest_submit_smoke`** (ignored). Env vars and recipe: [`migration-artifact-signing.md`](migration-artifact-signing.md#rest-hash-signing-gated-smoke-test). | +| **MSIX decoupled** | `PSIGN_MSIX_DLIB`, `PSIGN_MSIX_DMDF` | Repacks minimal MSIX, runs `msix-parity-sign.ps1 -UseDecoupledDigest` (same **`documented_rust_msix_sign_ex3_gap`** vs **`semantic_mismatch`** rules as embedded MSIX when sign exits differ). If you run **`run-parity-diff.ps1`** with those env vars set, **`artifact_msix_decoupled_semantic`** uses the same classification. | +| **Catalog verify** | `PSIGN_CATALOG_TARGET`, `PSIGN_CATALOG_FILE` | Runs `psign-tool verify --catalog `, then the same with `--os-version-check 386:10.0.26100.0`. Optional exhaustive parity records both as `artifact_catalog_*`. | +| **Artifact Signing decoupled** | `PSIGN_ARTIFACT_SIGNING_*` (see below) | Optional local-only parity: run `cargo test -p psign --test parity_signtool artifact_signing_decoupled_pe_executes -- --ignored --nocapture` when fixtures + Microsoft dlib/metadata are available. Not wired into GitHub Actions by default. | +| **Artifact Signing REST submit** | `PSIGN_ARTIFACT_SIGNING_REST_*` | Optional: build **`psign-tool`** with **`--features artifact-signing-rest`**, then run **`artifact_signing_rest_submit_smoke`** (ignored). Env vars and recipe: [`migration-artifact-signing.md`](migration-artifact-signing.md#rest-hash-signing-gated-smoke-test). | -**Artifact Signing env vars** (for the ignored test `artifact_signing_decoupled_pe_executes` and manual recipes): `SIGNTOOL_RS_ARTIFACT_SIGNING_UNSIGNED_PE`, `SIGNTOOL_RS_ARTIFACT_SIGNING_METADATA`, `SIGNTOOL_RS_ARTIFACT_SIGNING_TIMESTAMP_URL`, `SIGNTOOL_RS_ARTIFACT_SIGNING_TEST_PFX`, optional `SIGNTOOL_RS_ARTIFACT_SIGNING_TEST_PFX_PASSWORD`, and either `SIGNTOOL_RS_ARTIFACT_SIGNING_DLIB` **or** `SIGNTOOL_RS_ARTIFACT_SIGNING_DLIB_ROOT`. Details: [`migration-artifact-signing.md`](migration-artifact-signing.md#ci--gated-parity-recipe). +**Artifact Signing env vars** (for the ignored test `artifact_signing_decoupled_pe_executes` and manual recipes): `PSIGN_ARTIFACT_SIGNING_UNSIGNED_PE`, `PSIGN_ARTIFACT_SIGNING_METADATA`, `PSIGN_ARTIFACT_SIGNING_TIMESTAMP_URL`, `PSIGN_ARTIFACT_SIGNING_TEST_PFX`, optional `PSIGN_ARTIFACT_SIGNING_TEST_PFX_PASSWORD`, and either `PSIGN_ARTIFACT_SIGNING_DLIB` **or** `PSIGN_ARTIFACT_SIGNING_DLIB_ROOT`. Details: [`migration-artifact-signing.md`](migration-artifact-signing.md#ci--gated-parity-recipe). ## Scheduled portable SIP / CMS (`rust-sip-parity.yml`) @@ -51,7 +51,7 @@ These checks also run under **`ci-unix.yml`** / **`scripts/linux-portable-valida - Quick native ↔ Rust exit-code smoke over PE (and optional WinMD when env vars are set): [`scripts/sip-format-smoke.ps1`](../scripts/sip-format-smoke.ps1). - Reusable committed/generated fixture inventory: [`tests/fixtures/code-signing-vectors.json`](../tests/fixtures/code-signing-vectors.json). The committed corpus lives uncompressed under [`tests/fixtures/generated-unsigned/`](../tests/fixtures/generated-unsigned/) and [`tests/fixtures/generated-signed/`](../tests/fixtures/generated-signed/) so tests can reference fixture files directly. Regenerate it with [`scripts/ci/build-code-signing-vector-corpus.ps1`](../scripts/ci/build-code-signing-vector-corpus.ps1) (or unsigned-only samples with [`build-code-signing-vector-samples.ps1`](../scripts/ci/build-code-signing-vector-samples.ps1)). The matrix includes PE aliases, script encoding variants, package bundles/encrypted negatives, installer/catalog probes, WIM/ESD negatives, detached PKCS#7 inputs, optional-provider probe rows, and native SignTool reject classifications for unsupported-but-useful encoding rows. -- WinMD parity: Tier 1 CI sets `SIGNTOOL_RS_WINMD_UNSIGNED_FIXTURE` via [`pack-minimal-winmd.ps1`](../scripts/ci/pack-minimal-winmd.ps1) (PE bytes, `.winmd` extension). Locally, point `SIGNTOOL_RS_WINMD_UNSIGNED_FIXTURE` at any unsigned `.winmd` plus the same `SIGNTOOL_RS_TEST_PFX` / password as PE; optional `SIGNTOOL_RS_WINMD_TIMESTAMP_URL` for RFC3161 (CI mirrors `SIGNTOOL_RS_TIMESTAMP_URL`). +- WinMD parity: Tier 1 CI sets `PSIGN_WINMD_UNSIGNED_FIXTURE` via [`pack-minimal-winmd.ps1`](../scripts/ci/pack-minimal-winmd.ps1) (PE bytes, `.winmd` extension). Locally, point `PSIGN_WINMD_UNSIGNED_FIXTURE` at any unsigned `.winmd` plus the same `PSIGN_TEST_PFX` / password as PE; optional `PSIGN_WINMD_TIMESTAMP_URL` for RFC3161 (CI mirrors `PSIGN_TIMESTAMP_URL`). - To change the pinned Devolutions commit, edit `$CommitSha` in [`scripts/ci/bootstrap-devolutions-authenticode.ps1`](../scripts/ci/bootstrap-devolutions-authenticode.ps1). - If detached PKCS#7 generation fails on a future SDK, adjust [`scripts/ci/prepare-parity-fixtures.ps1`](../scripts/ci/prepare-parity-fixtures.ps1) (`/p7` fallback flags). - Scenario inventory and semantics are summarized in [`parity-matrix.md`](parity-matrix.md). diff --git a/docs/cli-parity-backlog.md b/docs/cli-parity-backlog.md index 771ebda..d5801a0 100644 --- a/docs/cli-parity-backlog.md +++ b/docs/cli-parity-backlog.md @@ -35,7 +35,7 @@ Prioritize implementation based on product need; SIP-backed format signing does | Area | Notes | |------|--------| -| `sign --rust-sip pe`, `SIGNTOOL_RS_RUST_SIP` | Post-sign Authenticode digest consistency vs PKCS#7 after `SignerSignEx3`; see `docs/rust-sip-architecture.md` | +| `sign --rust-sip pe`, `PSIGN_RUST_SIP` | Post-sign Authenticode digest consistency vs PKCS#7 after `SignerSignEx3`; see `docs/rust-sip-architecture.md` | | `verify --rust-sip-pe-digest-check` | Additive check after WinTrust on PE/WinMD — does not replace `WinVerifyTrust` | | `verify --rust-sip-all-digest-checks` | Enables every `--rust-sip-*-digest-check` for embedded verify; encrypted MSIX extensions fail explicitly in the MSIX checker | | `/dg` staging overlap | When Rust PKCS#7 encode exists, split-digest backlog intersects Tier 1a completion | diff --git a/docs/gap-analysis-signing-platforms.md b/docs/gap-analysis-signing-platforms.md index 7205fef..fce58bb 100644 --- a/docs/gap-analysis-signing-platforms.md +++ b/docs/gap-analysis-signing-platforms.md @@ -1,6 +1,6 @@ # Feature gap analysis: native signtool, AzureSignTool, Artifact Signing vs psign -This document compares **Windows SDK `signtool.exe`**, **AzureSignTool**, **Azure Artifact Signing (Trusted Signing)**, and this repository’s **`psign-tool-windows`** / **`psign-tool-portable`**. It is the product-facing companion to the engineering-focused [`rust-sip-gaps.md`](rust-sip-gaps.md) and [`parity-matrix.md`](parity-matrix.md). +This document compares **Windows SDK `signtool.exe`**, **AzureSignTool**, **Azure Artifact Signing (Trusted Signing)**, and this repository’s **`psign-tool`** / **`psign-tool portable`**. It is the product-facing companion to the engineering-focused [`rust-sip-gaps.md`](rust-sip-gaps.md) and [`parity-matrix.md`](parity-matrix.md). **Writable copies of Kits / System32 binaries (read-only install dirs):** [`writable-signing-binaries.md`](writable-signing-binaries.md). @@ -10,7 +10,7 @@ This document compares **Windows SDK `signtool.exe`**, **AzureSignTool**, **Azur Legend: **Sign** = produce/embed Authenticode; **WT verify** = `WinVerifyTrust`-style OS verify; **Digest** = recompute SIP indirect data vs PKCS#7; **Trust** = portable CMS + explicit anchors. -| Subject format | Native `signtool` | `psign-tool-windows` | `psign-tool-portable` | +| Subject format | Native `signtool` | `psign-tool` | `psign-tool portable` | |----------------|-------------------|--------------------|---------------------| | PE / WinMD | Sign, WT verify | Sign, WT verify, optional `--rust-sip pe` | Digest, inspect, trust-verify-pe | | CAB | Sign, WT verify | Same | verify-cab, trust-verify-cab, cab-digest | @@ -34,14 +34,14 @@ Legend: **Sign** = produce/embed Authenticode; **WT verify** = `WinVerifyTrust`- | Goal | Today | Gap | |------|--------|-----| | **Drop-in Linux replacement for `signtool.exe` sign/verify** | Not supported | Signing and WinTrust-backed verify require Windows CryptAPI/SIP (`SignerSignEx3`, `WinVerifyTrust`). | -| **Drop-in Linux replacement for AzureSignTool** | Partial | **`azure-key-vault-sign-digest`** on **`psign-tool-portable`** (**`--features azure-kv-sign-portable`**) performs the Key Vault **`keys/sign`** step (**digest file → signature**). Use **`pe-digest --encoding raw`** for the **PE image** hash file; use **`pe-signer-rs256-prehash --encoding raw`** (optional **`--signer-index`** for the *N*th **`SignerInfo`** inside the selected PKCS#7 row) when you need the **CMS authenticated-attribute** **SHA-256** prehash (**32** octets) for **`RS256`** on an **existing embedded PKCS#7** (see [`migration-azuresigntool.md`](migration-azuresigntool.md)). **Embedding** Authenticode still requires **`psign-tool-windows`** (`SignerSignEx3`) or a portable **`SignedData`** rebuild. Full **`sign`** with KV callback remains Windows (**`--features azure-kv-sign`**). | -| **Drop-in Linux replacement for Artifact Signing (dlib / REST)** | Partial | **`artifact-signing-submit`** (**`--features artifact-signing-rest`**) runs on **Linux/macOS** via **`psign-tool-portable`** or on Windows via **`psign-tool-windows`** — same **`:sign`** LRO (**hash → JSON**). **Embedding** PKCS#7 still requires **`SignerSignEx3`** + dlib or future portable CMS/embed. **`psign-tool-portable`** validates **`--dmdf`** JSON without network. | -| **Linux verify + digest parity for many Authenticode formats** | Supported | **`psign-tool-portable`** covers PE, CAB, MSI, ESD/WIM, cleartext MSIX, catalog, scripts; **`trust-verify-*`** adds anchor-based CMS trust (see [`authenticode-trust-stack.md`](authenticode-trust-stack.md)). | +| **Drop-in Linux replacement for AzureSignTool** | Partial | **`azure-key-vault-sign-digest`** on **`psign-tool portable`** (**`--features azure-kv-sign-portable`**) performs the Key Vault **`keys/sign`** step (**digest file → signature**). Use **`pe-digest --encoding raw`** for the **PE image** hash file; use **`pe-signer-rs256-prehash --encoding raw`** (optional **`--signer-index`** for the *N*th **`SignerInfo`** inside the selected PKCS#7 row) when you need the **CMS authenticated-attribute** **SHA-256** prehash (**32** octets) for **`RS256`** on an **existing embedded PKCS#7** (see [`migration-azuresigntool.md`](migration-azuresigntool.md)). **Embedding** Authenticode still requires **`psign-tool`** (`SignerSignEx3`) or a portable **`SignedData`** rebuild. Full **`sign`** with KV callback remains Windows (**`--features azure-kv-sign`**). | +| **Drop-in Linux replacement for Artifact Signing (dlib / REST)** | Partial | **`artifact-signing-submit`** (**`--features artifact-signing-rest`**) runs on **Linux/macOS** via **`psign-tool portable`** or on Windows via **`psign-tool`** — same **`:sign`** LRO (**hash → JSON**). **Embedding** PKCS#7 still requires **`SignerSignEx3`** + dlib or future portable CMS/embed. **`psign-tool portable`** validates **`--dmdf`** JSON without network. | +| **Linux verify + digest parity for many Authenticode formats** | Supported | **`psign-tool portable`** covers PE, CAB, MSI, ESD/WIM, cleartext MSIX, catalog, scripts; **`trust-verify-*`** adds anchor-based CMS trust (see [`authenticode-trust-stack.md`](authenticode-trust-stack.md)). | | **Maximum Authenticode subject formats** | Windows signs all SIP-registered types Rust can digest-check | **Encrypted MSIX**, **VBA/mso**, **extension SIP DLLs**, **standalone `.p7x`** subject handling — see [`rust-sip-gaps.md`](rust-sip-gaps.md). | -**Practical Linux path today:** Use **`psign-tool-portable`** for **digest computation**, **Key Vault `keys/sign`** on digest files (**`azure-key-vault-sign-digest`** with **`--features azure-kv-sign-portable`**), **`:sign` REST** (**`artifact-signing-submit`** with **`--features artifact-signing-rest`**), **inspect**, and **verify/trust** across supported formats. **Embed** Authenticode (PKCS#7 into the subject) still requires **`psign-tool-windows`** / **`SignerSignEx3`** (or native **`signtool.exe`**) until portable CMS+embed lands. Cookbook: [`linux-signing-pipelines.md`](linux-signing-pipelines.md). +**Practical Linux path today:** Use **`psign-tool portable`** for **digest computation**, **Key Vault `keys/sign`** on digest files (**`azure-key-vault-sign-digest`** with **`--features azure-kv-sign-portable`**), **`:sign` REST** (**`artifact-signing-submit`** with **`--features artifact-signing-rest`**), **inspect**, and **verify/trust** across supported formats. **Embed** Authenticode (PKCS#7 into the subject) still requires **`psign-tool`** / **`SignerSignEx3`** (or native **`signtool.exe`**) until portable CMS+embed lands. Cookbook: [`linux-signing-pipelines.md`](linux-signing-pipelines.md). -**Long-term Linux signing** (if required): implement portable **CMS `SignerInfo` production** (inside **`SignedData`**) + **format-specific embedding** (PE `WIN_CERTIFICATE`, CAB PKCS#7 placement, MSI digital signature streams, MSIX `ContentTypes` / manifest glue, etc.) and combine with **remote signing** (KV REST, Artifact Signing `:sign` LRO). [`pkcs7.rs`](crates/psign-sip-digest/src/pkcs7.rs) holds parse/replace helpers, **`signed_data_replace_first_signer_info`**, **`encode_pkcs7_content_info_signed_data_der`**, **RSA PKCS#1 RS256** prehash ↔ **`SignerInfo.signature`** parity tests (`rsa_pkcs1v15_signed_attrs_verify`), and **`signer_info_sha256_digest_over_signed_attrs`** (documented KV **`RS256`** input shape); [`pe_embed.rs`](crates/psign-sip-digest/src/pe_embed.rs) can **wrap PKCS#7**, **append** rows (including after signer splice experiments), and **recompute `CheckSum`**. **`psign-tool-portable pe-signer-rs256-prehash`** surfaces the **32-byte** prehash for Linux KV workflows; **unsigned→signed** / timestamp / CAB·MSI embed remain backlog (see [`rust-sip-gaps.md`](rust-sip-gaps.md)). +**Long-term Linux signing** (if required): implement portable **CMS `SignerInfo` production** (inside **`SignedData`**) + **format-specific embedding** (PE `WIN_CERTIFICATE`, CAB PKCS#7 placement, MSI digital signature streams, MSIX `ContentTypes` / manifest glue, etc.) and combine with **remote signing** (KV REST, Artifact Signing `:sign` LRO). [`pkcs7.rs`](crates/psign-sip-digest/src/pkcs7.rs) holds parse/replace helpers, **`signed_data_replace_first_signer_info`**, **`encode_pkcs7_content_info_signed_data_der`**, **RSA PKCS#1 RS256** prehash ↔ **`SignerInfo.signature`** parity tests (`rsa_pkcs1v15_signed_attrs_verify`), and **`signer_info_sha256_digest_over_signed_attrs`** (documented KV **`RS256`** input shape); [`pe_embed.rs`](crates/psign-sip-digest/src/pe_embed.rs) can **wrap PKCS#7**, **append** rows (including after signer splice experiments), and **recompute `CheckSum`**. **`psign-tool portable pe-signer-rs256-prehash`** surfaces the **32-byte** prehash for Linux KV workflows; **unsigned→signed** / timestamp / CAB·MSI embed remain backlog (see [`rust-sip-gaps.md`](rust-sip-gaps.md)). --- @@ -49,7 +49,7 @@ Legend: **Sign** = produce/embed Authenticode; **WT verify** = `WinVerifyTrust`- **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)). -**This repo (`psign-tool-windows`):** +**This repo (`psign-tool`):** | Area | Parity | |------|--------| @@ -59,7 +59,7 @@ Legend: **Sign** = produce/embed Authenticode; **WT verify** = `WinVerifyTrust`- | catdb | Partial | | Every obscure `/switch` | See **`cli-parity-backlog.md`** | -**Portable digest-only checks** after native sign: **`verify-pe`**, **`--rust-sip-*`** family on **`psign-tool-windows`**. +**Portable digest-only checks** after native sign: **`verify-pe`**, **`--rust-sip-*`** family on **`psign-tool`**. --- @@ -69,7 +69,7 @@ Legend: **Sign** = produce/embed Authenticode; **WT verify** = `WinVerifyTrust`- **This repo:** -| AzureSignTool concept | `psign-tool-windows` | `psign-tool-portable` | +| AzureSignTool concept | `psign-tool` | `psign-tool portable` | |-----------------------|-------------------|---------------------| | KV URL, cert name, auth (MI / SP / token) | Yes (`--features azure-kv-sign`) | **`azure-key-vault-sign-digest`** (`--features azure-kv-sign-portable`) — digest file only | | Batch / parallelism / exit HRESULTs | Mapped (`--input-file-list`, `--exit-codes azuresigntool`, …) | N/A | @@ -92,19 +92,19 @@ Details: [`migration-azuresigntool.md`](migration-azuresigntool.md). | Surface | Implementation | |---------|----------------| -| Decoupled sign (`--dlib`, `--trusted-signing-dlib-root`, `--dmdf`) | **`psign-tool-windows`** only | -| REST hash signing | **`artifact-signing-submit`** (`--features artifact-signing-rest`) on **`psign-tool-windows`** or **`psign-tool-portable`** | -| Metadata validation without signing | **`psign-tool-portable artifact-signing-metadata-check`** | +| Decoupled sign (`--dlib`, `--trusted-signing-dlib-root`, `--dmdf`) | **`psign-tool`** only | +| REST hash signing | **`artifact-signing-submit`** (`--features artifact-signing-rest`) on **`psign-tool`** or **`psign-tool portable`** | +| Metadata validation without signing | **`psign-tool portable artifact-signing-metadata-check`** | **Gap:** REST output is **not** wired into a portable Authenticode embedder; docs state MVP is hash signing / diagnostics. [`migration-artifact-signing.md`](migration-artifact-signing.md). --- -## `psign-tool-portable` (Linux/macOS) +## `psign-tool portable` (Linux/macOS) -**Commands (verify / inspect / digest tools):** See [`roadmap-authenticode-linux.md`](roadmap-authenticode-linux.md) and **`psign-tool-portable --help`**. +**Commands (verify / inspect / digest tools):** See [`roadmap-authenticode-linux.md`](roadmap-authenticode-linux.md) and **`psign-tool portable --help`**. -**Remote signing steps (no embed):** With **`--features azure-kv-sign-portable`**, **`azure-key-vault-sign-digest`** performs Azure Key Vault **`keys/sign`** on a **raw digest file** (same REST shape as AzureSignTool’s remote step). **`pe-signer-rs256-prehash`**, **`cab-signer-rs256-prehash`**, **`msi-signer-rs256-prehash`**, and **`catalog-signer-rs256-prehash`** (**`--encoding raw`**) emit the **32-byte** **`RS256`** input over **`SignerInfo.signedAttrs`** (distinct from subject-layout digests and from **`verify-catalog`**’s CTL **`eContent`** / PKCS#9 checks). With **`--features artifact-signing-rest`**, **`artifact-signing-submit`** calls Trusted Signing **`:sign`**. Neither writes PKCS#7 into a PE/CAB/MSI subject without **`psign-tool-windows`** (or future portable CMS embed). +**Remote signing steps (no embed):** With **`--features azure-kv-sign-portable`**, **`azure-key-vault-sign-digest`** performs Azure Key Vault **`keys/sign`** on a **raw digest file** (same REST shape as AzureSignTool’s remote step). **`pe-signer-rs256-prehash`**, **`cab-signer-rs256-prehash`**, **`msi-signer-rs256-prehash`**, and **`catalog-signer-rs256-prehash`** (**`--encoding raw`**) emit the **32-byte** **`RS256`** input over **`SignerInfo.signedAttrs`** (distinct from subject-layout digests and from **`verify-catalog`**’s CTL **`eContent`** / PKCS#9 checks). With **`--features artifact-signing-rest`**, **`artifact-signing-submit`** calls Trusted Signing **`:sign`**. Neither writes PKCS#7 into a PE/CAB/MSI subject without **`psign-tool`** (or future portable CMS embed). **RFC 3161 TSA helpers (Linux-side, no embed):** **`rfc3161-timestamp-req`** builds **`TimeStampReq`** DER from **`--digest-hex`** / **`--digest-file`** (message-imprint preimage; optional **`--nonce`**, **`--cert-req`**) for **`curl`** / OpenSSL **`ts`** against a timestamp URL. **`rfc3161-timestamp-resp-inspect`** prints **`pki_status`** / **`pki_status_int`** (raw status INTEGER) / **`granted`** / token length, **`time_stamp_token_prefix_hex`** (first **16** octets of the raw **`timeStampToken`** TLV, or **`-`** when absent — handy for **`ContentInfo`** / CMS shape checks), **`status_strings_json`** (**`PKIFreeText`**), **`fail_info_tlv_hex`**, and **`fail_info_flags_json`** (RFC 2510 Appendix A **`PKIFailureInfo`** bit names through **`badPOP`**, then **`bit_N`**; **`null`** when the **`BIT STRING`** body is not decodable). Optional **`rfc3161-timestamp-http-post`** (**`--features timestamp-http`**) performs the HTTPS POST without **`curl`**. None of this replaces **`SignerTimeStampEx3`** or **`CryptVerifyTimeStampSignature`**. @@ -128,7 +128,7 @@ Use **public documentation**, **this repo’s parity tests**, and **writable cop | Original / surface | Mechanism | Typical study angle | |--------------------|-----------|----------------------| -| Windows SDK **`signtool.exe`** | Native PE | Writable **`signtool.exe`**; map **`SignerSignEx3`**, **`WinVerifyTrust`** to docs and `psign-tool-windows` paths | +| Windows SDK **`signtool.exe`** | Native PE | Writable **`signtool.exe`**; map **`SignerSignEx3`**, **`WinVerifyTrust`** to docs and `psign-tool` paths | | **`mssign32.dll`**, **`crypt32.dll`**, **`WINTRUST.dll`** | Native PE | Writable copies; follow **`SignerSignEx3`**, **`CryptMsg*`**, SIP glue vs [`windows-signing-components.md`](windows-signing-components.md) | | **AzureSignTool** | .NET | **`AzureSignTool.dll`** / **`AzureSign.Core.dll`** vs [`psign-azure-kv-rest`](../crates/psign-azure-kv-rest/) and [`migration-azuresigntool.md`](migration-azuresigntool.md) | | **Artifact Signing** managed client | .NET | **`Microsoft.ArtifactSigning.Client.dll`** vs [`psign-codesigning-rest`](../crates/psign-codesigning-rest/) | @@ -143,7 +143,7 @@ When filing issues, prefer **parity scenario IDs** from [`parity-matrix.md`](par | Tier | Command / script | Platform | |------|-------------------|----------| | Unix CI | `cargo digest-test` / workflows in **`ci-unix.yml`** | Linux | -| Unix local mirror | **`scripts/linux-portable-validation.sh`** (from repo root; bash); **`psign-tool-portable append-pe-pkcs7`** / **`pe-checksum --strict`** for PE layout experiments | Linux / WSL / Git Bash | +| Unix local mirror | **`scripts/linux-portable-validation.sh`** (from repo root; bash); **`psign-tool portable append-pe-pkcs7`** / **`pe-checksum --strict`** for PE layout experiments | Linux / WSL / Git Bash | | Pipelines narrative | [`linux-signing-pipelines.md`](linux-signing-pipelines.md) | Linux-focused | | Windows parity | `./scripts/run-parity-diff.ps1`, `./scripts/ci/run-exhaustive-parity-ci.ps1` | Windows | | Writable native signing binaries | **`pwsh -File scripts/prepare-writable-signing-binaries.ps1`** → **`parity-output/writable-signing-binaries`** (gitignored) | Windows | diff --git a/docs/linux-signing-pipelines.md b/docs/linux-signing-pipelines.md index d28529e..e16b047 100644 --- a/docs/linux-signing-pipelines.md +++ b/docs/linux-signing-pipelines.md @@ -1,6 +1,6 @@ # Linux signing pipelines (what works today) -**`psign-tool-portable`** on Linux/macOS does **not** embed Authenticode PKCS#7 into PE/CAB/MSIX yet (`pe_embed.rs` / CMS producer stubs — see [`rust-sip-gaps.md`](rust-sip-gaps.md)). This page describes **practical hybrid** flows and **verify-only** flows. +**`psign-tool portable`** on Linux/macOS does **not** embed Authenticode PKCS#7 into PE/CAB/MSIX yet (`pe_embed.rs` / CMS producer stubs — see [`rust-sip-gaps.md`](rust-sip-gaps.md)). This page describes **practical hybrid** flows and **verify-only** flows. For tool-by-tool gaps vs **`signtool.exe`**, AzureSignTool, and Artifact Signing, see [`gap-analysis-signing-platforms.md`](gap-analysis-signing-platforms.md). On Windows, for writable copies of native signing binaries outside protected install paths, see [`writable-signing-binaries.md`](writable-signing-binaries.md). @@ -18,36 +18,36 @@ Automation: **`cargo digest-test`**, **`scripts/linux-portable-validation.sh`**, ## 1.5 RFC 3161 TSA query/reply (DER only; no embed) -**`psign-tool-portable rfc3161-timestamp-req`** builds **`TimeStampReq`** DER from **`--digest-hex`** / **`--digest-file`** (message-imprint preimage; optional **`--nonce`**, **`--cert-req`**). **`rfc3161-timestamp-resp-inspect`** prints **`pki_status`** / **`pki_status_int`** (raw **`PKIStatus`** INTEGER) / **`granted`** / token length, **`time_stamp_token_prefix_hex`** (first **16** octets of the **`timeStampToken`** TLV), **`status_strings_json`**, **`fail_info_tlv_hex`**, and **`fail_info_flags_json`** from **`TimeStampResp`** DER. Build with **`--features timestamp-http`** for **`rfc3161-timestamp-http-post --url …`** (Rustls POST **`application/timestamp-query`**, response DER to stdout / **`--output`**); otherwise use **`curl`** or OpenSSL **`ts`**. Wiring the token into **`SignerInfo`** as an Authenticode countersignature still goes through **`psign-tool-windows`** / **`SignerTimeStampEx3`** (or future portable CMS) today. +**`psign-tool portable rfc3161-timestamp-req`** builds **`TimeStampReq`** DER from **`--digest-hex`** / **`--digest-file`** (message-imprint preimage; optional **`--nonce`**, **`--cert-req`**). **`rfc3161-timestamp-resp-inspect`** prints **`pki_status`** / **`pki_status_int`** (raw **`PKIStatus`** INTEGER) / **`granted`** / token length, **`time_stamp_token_prefix_hex`** (first **16** octets of the **`timeStampToken`** TLV), **`status_strings_json`**, **`fail_info_tlv_hex`**, and **`fail_info_flags_json`** from **`TimeStampResp`** DER. Build with **`--features timestamp-http`** for **`rfc3161-timestamp-http-post --url …`** (Rustls POST **`application/timestamp-query`**, response DER to stdout / **`--output`**); otherwise use **`curl`** or OpenSSL **`ts`**. Wiring the token into **`SignerInfo`** as an Authenticode countersignature still goes through **`psign-tool`** / **`SignerTimeStampEx3`** (or future portable CMS) today. ## 2. Azure Artifact Signing — digest + REST on Linux, embed on Windows -Build **`psign-tool-portable`** with **`--features artifact-signing-rest`**. +Build **`psign-tool portable`** with **`--features artifact-signing-rest`**. 1. **Subject digest** (raw bytes for REST body): ```bash - psign-tool-portable pe-digest --algorithm sha256 --encoding raw --output digest.bin ./MyApp.exe + psign-tool portable pe-digest --algorithm sha256 --encoding raw --output digest.bin ./MyApp.exe # CAB: - psign-tool-portable cab-digest --algorithm sha256 --encoding raw --output digest.bin ./My.cab + psign-tool portable cab-digest --algorithm sha256 --encoding raw --output digest.bin ./My.cab # CMS RS256 prehash on signed CAB (KV keys/sign), not cab-digest: - # psign-tool-portable cab-signer-rs256-prehash --encoding raw --output signer-prehash.bin ./My.cab + # psign-tool portable cab-signer-rs256-prehash --encoding raw --output signer-prehash.bin ./My.cab # Same for MSI (DigitalSignature stream), not installer fingerprint digest: - # psign-tool-portable msi-signer-rs256-prehash --encoding raw --output signer-prehash.bin ./My.msi + # psign-tool portable msi-signer-rs256-prehash --encoding raw --output signer-prehash.bin ./My.msi # Whole-file PKCS#7 .cat (same 32-byte digest as pkcs7-signer-rs256-prehash on that DER): - # psign-tool-portable catalog-signer-rs256-prehash --encoding raw --output signer-prehash.bin ./My.cat + # psign-tool portable catalog-signer-rs256-prehash --encoding raw --output signer-prehash.bin ./My.cat ``` -2. **`:sign` LRO** (same as **`psign-tool-windows artifact-signing-submit`**): +2. **`:sign` LRO** (same as **`psign-tool artifact-signing-submit`**): ```bash - psign-tool-portable artifact-signing-submit \ + psign-tool portable artifact-signing-submit \ --region REGION --account-name ACCOUNT --profile-name PROFILE \ --digest-file digest.bin --signature-algorithm RS256 \ --managed-identity # or --access-token / tenant + client-id + client-secret ``` -3. **Embed** PKCS#7 / complete Authenticode: still **`psign-tool-windows`** + **`SignerSignEx3`** (and typically **`--dlib`** / **`--dmdf`** for Trusted Signing) until a portable embedder exists. +3. **Embed** PKCS#7 / complete Authenticode: still **`psign-tool`** + **`SignerSignEx3`** (and typically **`--dlib`** / **`--dmdf`** for Trusted Signing) until a portable embedder exists. Optional debug: **`SIGNTOOL_PORTABLE_DEBUG=1`**. @@ -66,7 +66,7 @@ Details: [`migration-artifact-signing.md`](migration-artifact-signing.md). Then **`azure-key-vault-sign-digest`** with **`--features azure-kv-sign-portable`** performs **`keys/sign`** (see [`migration-azuresigntool.md`](migration-azuresigntool.md)). **`verify-catalog`** checks CTL-style **`messageDigest` ↔ eContent`** and can disagree with Authenticode-only PKCS#7 bodies—use the right command for catalog *membership* vs *CMS signer* prehash. -**Embed** PKCS#7 on Windows with **`psign-tool-windows`** (`--features azure-kv-sign`) or native **`signtool.exe`**. +**Embed** PKCS#7 on Windows with **`psign-tool`** (`--features azure-kv-sign`) or native **`signtool.exe`**. Details: [`migration-azuresigntool.md`](migration-azuresigntool.md). @@ -74,6 +74,6 @@ Details: [`migration-azuresigntool.md`](migration-azuresigntool.md). Ordered backlog (engineering): [`roadmap-authenticode-linux.md`](roadmap-authenticode-linux.md) (Phase 2 stretch: PKCS#7 + PE **`WIN_CERTIFICATE`**, then CAB/MSI/MSIX). SIP coverage limits: [`rust-sip-gaps.md`](rust-sip-gaps.md). -**PE checksum:** **`psign-tool-portable pe-checksum ./file.exe`** compares optional-header **`CheckSum`** to **`pe_compute_image_checksum`** (same algorithm used after **`append-pe-pkcs7`**). **`--strict`** fails when they differ. +**PE checksum:** **`psign-tool portable pe-checksum ./file.exe`** compares optional-header **`CheckSum`** to **`pe_compute_image_checksum`** (same algorithm used after **`append-pe-pkcs7`**). **`--strict`** fails when they differ. -**Library + CLI:** [`psign-sip-digest::pkcs7`](../crates/psign-sip-digest/src/pkcs7.rs) exposes **`parse_pe_pkcs7_spc_indirect_data`** (read **`SpcIndirectDataContent`** from an embedded PE PKCS#7), **`spc_indirect_data_replace_message_digest`** (swap the **`messageDigest`** octets while keeping **`SpcPeImageData`**), **`cms_digest_encapsulated_econtent_bytes`** / **`signer_info_pkcs9_message_digest_octets`** (RFC 5652 **`eContent`** hash vs PKCS#9 **`messageDigest`** — matches RustCrypto **`cms` SignerInfoBuilder** semantics), **`signer_info_signed_attributes_sequence_der`** (**`SET OF Attribute`** DER for §5.4 authenticated-attribute signing — compare **`CryptMsg`** / **`SignerInfoBuilder`** inputs when wiring KV **`:sign`**), **`signed_attributes_replace_pkcs9_message_digest`** (rewrite PKCS#9 **`messageDigest`** in the authenticated-attribute **`SET`** after **`encapContentInfo`** changes — still need new **`encryptedDigest`**), **`signer_info_sha256_digest_over_signed_attrs`** (**SHA-256** over that **`SET`** — validate vs **`CryptMsg`** / **KV `RS256`** before production), **`signer_info_clone_with_signed_attrs`** / **`signer_info_clone_with_signature_octets`** (apply rebuilt attrs / remote **`encryptedDigest`** octets), **`signed_data_replace_signer_info_at`** / **`signed_data_replace_first_signer_info`** (splice **`SignerInfo`** back into **`SignedData.signerInfos`**), and **`signed_data_replace_encapsulated_spc_indirect`** (rewrite **`SignedData.encapContentInfo.eContent`** — **`SignerInfo`** signature becomes invalid until rebuilt; see doc comment). On Linux, **`psign-tool-portable pe-signer-rs256-prehash ./file.exe`** (**`--encoding raw`**, optional **`--signer-index`** for the *N*th **`SignerInfo`** in the PKCS#7 row selected by **`--index`**) emits the **32-byte** **`RS256`** digest for Azure Key Vault **`keys/sign`** (CMS authenticated-attribute **`SET`** §5.4 — distinct from **`pe-digest`** image hash). **`psign-tool-portable pkcs7-signer-rs256-prehash ./blob.p7`** (**`--signer-index 0`**, **`--encoding raw`**) computes the same digest from PKCS#7 DER alone (for example **`extract-pe-pkcs7 --output`** first). **`psign-tool-portable inspect-pe-spc-indirect ./file.exe`** prints JSON (OIDs, digest hex, SIP match flag) for the same structure—use **`--index N`** to match the *N*th PKCS#7 row (**`list-pe-pkcs7`** / **`extract-pe-pkcs7`** order)—useful before a portable **`SignedData`** / **`WIN_CERTIFICATE`** rebuild exists. **`psign-tool-portable extract-pe-pkcs7 ./file.exe`** writes embedded PKCS#7 DER to stdout (or **`--output`**); use **`--index N`** for the *N*th **`WIN_CERT_TYPE_PKCS_SIGNED_DATA`** row (multi-signed binaries). **`psign-tool-portable list-pe-pkcs7 ./file.exe`** prints **`pkcs7_entries`** and each row’s **`byte_len`** (same index order as **`extract-pe-pkcs7`**). **`psign-tool-portable append-pe-pkcs7 --pe in.exe --pkcs7 blob.der --output out.exe`** appends a PKCS#7 row via **`pe_embed`** and refreshes the PE **image checksum** (experimental — not a full CMS signer). +**Library + CLI:** [`psign-sip-digest::pkcs7`](../crates/psign-sip-digest/src/pkcs7.rs) exposes **`parse_pe_pkcs7_spc_indirect_data`** (read **`SpcIndirectDataContent`** from an embedded PE PKCS#7), **`spc_indirect_data_replace_message_digest`** (swap the **`messageDigest`** octets while keeping **`SpcPeImageData`**), **`cms_digest_encapsulated_econtent_bytes`** / **`signer_info_pkcs9_message_digest_octets`** (RFC 5652 **`eContent`** hash vs PKCS#9 **`messageDigest`** — matches RustCrypto **`cms` SignerInfoBuilder** semantics), **`signer_info_signed_attributes_sequence_der`** (**`SET OF Attribute`** DER for §5.4 authenticated-attribute signing — compare **`CryptMsg`** / **`SignerInfoBuilder`** inputs when wiring KV **`:sign`**), **`signed_attributes_replace_pkcs9_message_digest`** (rewrite PKCS#9 **`messageDigest`** in the authenticated-attribute **`SET`** after **`encapContentInfo`** changes — still need new **`encryptedDigest`**), **`signer_info_sha256_digest_over_signed_attrs`** (**SHA-256** over that **`SET`** — validate vs **`CryptMsg`** / **KV `RS256`** before production), **`signer_info_clone_with_signed_attrs`** / **`signer_info_clone_with_signature_octets`** (apply rebuilt attrs / remote **`encryptedDigest`** octets), **`signed_data_replace_signer_info_at`** / **`signed_data_replace_first_signer_info`** (splice **`SignerInfo`** back into **`SignedData.signerInfos`**), and **`signed_data_replace_encapsulated_spc_indirect`** (rewrite **`SignedData.encapContentInfo.eContent`** — **`SignerInfo`** signature becomes invalid until rebuilt; see doc comment). On Linux, **`psign-tool portable pe-signer-rs256-prehash ./file.exe`** (**`--encoding raw`**, optional **`--signer-index`** for the *N*th **`SignerInfo`** in the PKCS#7 row selected by **`--index`**) emits the **32-byte** **`RS256`** digest for Azure Key Vault **`keys/sign`** (CMS authenticated-attribute **`SET`** §5.4 — distinct from **`pe-digest`** image hash). **`psign-tool portable pkcs7-signer-rs256-prehash ./blob.p7`** (**`--signer-index 0`**, **`--encoding raw`**) computes the same digest from PKCS#7 DER alone (for example **`extract-pe-pkcs7 --output`** first). **`psign-tool portable inspect-pe-spc-indirect ./file.exe`** prints JSON (OIDs, digest hex, SIP match flag) for the same structure—use **`--index N`** to match the *N*th PKCS#7 row (**`list-pe-pkcs7`** / **`extract-pe-pkcs7`** order)—useful before a portable **`SignedData`** / **`WIN_CERTIFICATE`** rebuild exists. **`psign-tool portable extract-pe-pkcs7 ./file.exe`** writes embedded PKCS#7 DER to stdout (or **`--output`**); use **`--index N`** for the *N*th **`WIN_CERT_TYPE_PKCS_SIGNED_DATA`** row (multi-signed binaries). **`psign-tool portable list-pe-pkcs7 ./file.exe`** prints **`pkcs7_entries`** and each row’s **`byte_len`** (same index order as **`extract-pe-pkcs7`**). **`psign-tool portable append-pe-pkcs7 --pe in.exe --pkcs7 blob.der --output out.exe`** appends a PKCS#7 row via **`pe_embed`** and refreshes the PE **image checksum** (experimental — not a full CMS signer). diff --git a/docs/migration-artifact-signing.md b/docs/migration-artifact-signing.md index 405c58d..99dc4dc 100644 --- a/docs/migration-artifact-signing.md +++ b/docs/migration-artifact-signing.md @@ -1,19 +1,19 @@ -# Azure Trusted Signing (Artifact Signing) with psign-tool-windows +# Azure Trusted Signing (Artifact Signing) with psign-tool Microsoft **Artifact Signing** (often called **Trusted Signing**) integrates with native **SignTool** through a **decoupled digest DLL** (`Azure.CodeSigning.Dlib.dll`) and a **JSON metadata file** consumed via **`/dmdf`**. Official setup: [Set up signing integrations](https://learn.microsoft.com/azure/artifact-signing/how-to-signing-integrations) and the [Microsoft.ArtifactSigning.Client](https://www.nuget.org/packages/Microsoft.ArtifactSigning.Client) package. -**psign-tool-windows** uses the same Win32 bridge as SignTool: **`SignerSignEx3`** with **`SIGNER_DIGEST_SIGN_INFO`** pointing at the DLL exports (this repo prefers **`AuthenticodeDigestSignExWithFileHandle`** when present, matching Microsoft’s Azure dlib). +**psign-tool** uses the same Win32 bridge as SignTool: **`SignerSignEx3`** with **`SIGNER_DIGEST_SIGN_INFO`** pointing at the DLL exports (this repo prefers **`AuthenticodeDigestSignExWithFileHandle`** when present, matching Microsoft’s Azure dlib). -**psign-tool-portable** cannot load the mixed-mode/.NET dlib or call **`SignerSignEx3`**; use it **after** embedding for digest consistency checks and **anchor-based trust verification** (see [Portable post-sign verification](#portable-post-sign-verification) below). With **`--features artifact-signing-rest`** it can still call the same **`:sign`** REST LRO as **`psign-tool-windows`** (hash in → JSON out — embedding remains a separate step). +**psign-tool portable** cannot load the mixed-mode/.NET dlib or call **`SignerSignEx3`**; use it **after** embedding for digest consistency checks and **anchor-based trust verification** (see [Portable post-sign verification](#portable-post-sign-verification) below). With **`--features artifact-signing-rest`** it can still call the same **`:sign`** REST LRO as **`psign-tool`** (hash in → JSON out — embedding remains a separate step). ### Optional: Azure Code Signing **REST** hash signing (experimental) PowerShell OpenAuthenticode can sign via the **`Azure.CodeSigning.Sdk`** client against the same **data-plane** API documented in Azure REST specs (**`CertificateProfileOperations_Sign`**, host template **`https://{region}.codesigning.azure.net/`**, OAuth scope **`https://codesigning.azure.net/.default`**). -With **`cargo build -p psign --features artifact-signing-rest --bin psign-tool-windows`**: +With **`cargo build -p psign --features artifact-signing-rest --bin psign-tool`**: ```powershell -psign-tool-windows.exe artifact-signing-submit ` +psign-tool.exe artifact-signing-submit ` --region westus ` --account-name myAccount ` --profile-name myProfile ` @@ -24,7 +24,7 @@ psign-tool-windows.exe artifact-signing-submit ` This runs the **`:sign`** LRO and prints the final JSON (**`signature`**, **`signingCertificate`**, …). It does **not** embed an Authenticode PKCS#7 into a PE by itself — combine with your signing pipeline or continue using **`--dlib`** / **`--trusted-signing-dlib-root`** for **`SignerSignEx3`** embedding. -#### Linux / CI: same REST helper from **`psign-tool-portable`** +#### Linux / CI: same REST helper from **`psign-tool portable`** Build or install with **`--features artifact-signing-rest`**, then use **`artifact-signing-submit`** with the same flags as Windows. Produce a raw Authenticode digest file from an unsigned PE with **`pe-digest --encoding raw --output digest.bin`** (SHA-256 → 32 bytes). @@ -32,17 +32,17 @@ Build or install with **`--features artifact-signing-rest`**, then use **`artifa ```bash cargo build -p psign-digest-cli --features artifact-signing-rest --locked -./target/debug/psign-tool-portable pe-digest --algorithm sha256 --encoding raw --output digest.bin ./MyApp.exe -./target/debug/psign-tool-portable artifact-signing-submit \ +./target/debug/psign-tool portable pe-digest --algorithm sha256 --encoding raw --output digest.bin ./MyApp.exe +./target/debug/psign-tool portable artifact-signing-submit \ --region westus --account-name myAccount --profile-name myProfile \ --digest-file digest.bin --signature-algorithm RS256 --managed-identity ``` Optional debug logs: **`SIGNTOOL_PORTABLE_DEBUG=1`**. -## Flag mapping (Microsoft sample → psign-tool-windows) +## Flag mapping (Microsoft sample → psign-tool) -| SignTool / docs | psign-tool-windows | +| SignTool / docs | psign-tool | |-----------------|------------------| | `/dlib` path to `Azure.CodeSigning.Dlib.dll` | `--dlib ` | | Same, but NuGet extract root | `--trusted-signing-dlib-root ` → resolves to `\bin\x64\Azure.CodeSigning.Dlib.dll` or `\bin\x86\...` matching **this executable’s** architecture (`cfg!(target_pointer_width)`) | @@ -58,7 +58,7 @@ Optional debug logs: **`SIGNTOOL_PORTABLE_DEBUG=1`**. Adjust paths to your extracted NuGet layout and metadata file: ```powershell -psign-tool-windows.exe sign ` +psign-tool.exe sign ` --digest sha256 ` --timestamp-url http://timestamp.acs.microsoft.com/ ` --timestamp-digest sha256 ` @@ -71,7 +71,7 @@ psign-tool-windows.exe sign ` Or pass the DLL explicitly: ```powershell -psign-tool-windows.exe sign ` +psign-tool.exe sign ` --digest sha256 ` --timestamp-url http://timestamp.acs.microsoft.com/ ` --timestamp-digest sha256 ` @@ -90,9 +90,9 @@ Follow Microsoft’s documented shape: regional **`Endpoint`**, **`CodeSigningAc Validate checked-in templates **without signing** using portable **`artifact-signing-metadata-check`**: ```bash -psign-tool-portable artifact-signing-metadata-check --path ./artifact-signing-metadata.json +psign-tool portable artifact-signing-metadata-check --path ./artifact-signing-metadata.json # or -cat ./artifact-signing-metadata.json | psign-tool-portable artifact-signing-metadata-check +cat ./artifact-signing-metadata.json | psign-tool portable artifact-signing-metadata-check ``` ## Runtime layout: NuGet `bin\x64` or `bin\x86` @@ -102,7 +102,7 @@ Deploy the **full** `bin\x64` or `bin\x86` folder from the NuGet package next to Prerequisites: - **.NET 8** runtime where Microsoft’s tooling expects it. -- **Architecture match**: use **x64** dlib with **64-bit** `psign-tool-windows`, **x86** with **32-bit** builds. Mismatch commonly surfaces as **`LoadLibraryW` failures** (see troubleshooting). +- **Architecture match**: use **x64** dlib with **64-bit** `psign-tool`, **x86** with **32-bit** builds. Mismatch commonly surfaces as **`LoadLibraryW` failures** (see troubleshooting). ### Troubleshooting `LoadLibraryW` failures @@ -110,13 +110,13 @@ When **`--dlib`** (or the path resolved from **`--trusted-signing-dlib-root`**) 1. **.NET 8** is installed and repairable on the machine. 2. The **entire** `bin\` directory from the NuGet package is deployed so dependent DLLs resolve. -3. **PE architecture** of **`Azure.CodeSigning.Dlib.dll`** matches **`psign-tool-windows`** (x64 vs x86). +3. **PE architecture** of **`Azure.CodeSigning.Dlib.dll`** matches **`psign-tool`** (x64 vs x86). ## Conflict matrix: Artifact Signing vs Azure Key Vault **Artifact Signing** uses **decoupled digest** mode only (**`--dlib`** or **`--trusted-signing-dlib-root`** **+** **`--dmdf`**). -**Azure Key Vault** signing (**`--azure-key-vault-url`** and related flags) is a **separate** implementation path. **`psign-tool-windows` rejects combining Key Vault options with `--dlib`, `--dmdf`, or `--trusted-signing-dlib-root`.** +**Azure Key Vault** signing (**`--azure-key-vault-url`** and related flags) is a **separate** implementation path. **`psign-tool` rejects combining Key Vault options with `--dlib`, `--dmdf`, or `--trusted-signing-dlib-root`.** If your team uses both workflows, keep them on **different invocations** or build targets—do not mix flags on one command line. @@ -124,7 +124,7 @@ For migrating from **AzureSignTool** (KV-focused CLI), see [`migration-azuresign ## Portable post-sign verification -On Linux/macOS (or Windows without the dlib), use **`psign-tool-portable`** after the signed artifact exists: +On Linux/macOS (or Windows without the dlib), use **`psign-tool portable`** after the signed artifact exists: 1. **`verify-pe`** — PKCS#7 indirect digest vs recomputed PE digest (no trust anchors). 2. **`trust-verify-pe`** — CMS validation **plus** explicit anchor trust (**`--anchor-dir`**, **`--authroot-cab`**) and policy options. @@ -139,8 +139,8 @@ Short-lived signing certificates **require a valid RFC3161 timestamp** for verif Example: ```bash -psign-tool-portable verify-pe ./MyApp.exe -psign-tool-portable trust-verify-pe ./MyApp.exe \ +psign-tool portable verify-pe ./MyApp.exe +psign-tool portable trust-verify-pe ./MyApp.exe \ --prefer-timestamp-signing-time \ --require-valid-timestamp \ --anchor-dir ./anchors \ @@ -159,15 +159,15 @@ Required-style variables when running that test locally: | Variable | Purpose | |----------|---------| -| `SIGNTOOL_RS_ARTIFACT_SIGNING_UNSIGNED_PE` | Unsigned PE to copy and sign | -| `SIGNTOOL_RS_ARTIFACT_SIGNING_METADATA` | Path to `--dmdf` JSON | -| `SIGNTOOL_RS_ARTIFACT_SIGNING_DLIB` | Explicit `--dlib` path (**or** use root below) | -| `SIGNTOOL_RS_ARTIFACT_SIGNING_DLIB_ROOT` | NuGet extract root for `--trusted-signing-dlib-root` | -| `SIGNTOOL_RS_ARTIFACT_SIGNING_TIMESTAMP_URL` | RFC3161 URL (e.g. ACS) | -| `SIGNTOOL_RS_ARTIFACT_SIGNING_TEST_PFX` | PFX for cert selection in this tool’s store/PFX path | -| `SIGNTOOL_RS_ARTIFACT_SIGNING_TEST_PFX_PASSWORD` | Optional PFX password | +| `PSIGN_ARTIFACT_SIGNING_UNSIGNED_PE` | Unsigned PE to copy and sign | +| `PSIGN_ARTIFACT_SIGNING_METADATA` | Path to `--dmdf` JSON | +| `PSIGN_ARTIFACT_SIGNING_DLIB` | Explicit `--dlib` path (**or** use root below) | +| `PSIGN_ARTIFACT_SIGNING_DLIB_ROOT` | NuGet extract root for `--trusted-signing-dlib-root` | +| `PSIGN_ARTIFACT_SIGNING_TIMESTAMP_URL` | RFC3161 URL (e.g. ACS) | +| `PSIGN_ARTIFACT_SIGNING_TEST_PFX` | PFX for cert selection in this tool’s store/PFX path | +| `PSIGN_ARTIFACT_SIGNING_TEST_PFX_PASSWORD` | Optional PFX password | -Either **`SIGNTOOL_RS_ARTIFACT_SIGNING_DLIB`** or **`SIGNTOOL_RS_ARTIFACT_SIGNING_DLIB_ROOT`** must be set; the test prefers **`_DLIB`** when both are present. +Either **`PSIGN_ARTIFACT_SIGNING_DLIB`** or **`PSIGN_ARTIFACT_SIGNING_DLIB_ROOT`** must be set; the test prefers **`_DLIB`** when both are present. @@ -182,18 +182,18 @@ cargo test -p psign --features artifact-signing-rest ` | Variable | Purpose | |----------|---------| -| `SIGNTOOL_RS_ARTIFACT_SIGNING_REST_REGION` | Regional segment (e.g. `westus`) | -| `SIGNTOOL_RS_ARTIFACT_SIGNING_REST_ACCOUNT_NAME` | Code signing account name | -| `SIGNTOOL_RS_ARTIFACT_SIGNING_REST_PROFILE_NAME` | Certificate profile name | -| `SIGNTOOL_RS_ARTIFACT_SIGNING_REST_DIGEST_FILE` | Path to raw digest bytes | -| `SIGNTOOL_RS_ARTIFACT_SIGNING_REST_SIGNATURE_ALGORITHM` | Optional (default API/`RS256`) | +| `PSIGN_ARTIFACT_SIGNING_REST_REGION` | Regional segment (e.g. `westus`) | +| `PSIGN_ARTIFACT_SIGNING_REST_ACCOUNT_NAME` | Code signing account name | +| `PSIGN_ARTIFACT_SIGNING_REST_PROFILE_NAME` | Certificate profile name | +| `PSIGN_ARTIFACT_SIGNING_REST_DIGEST_FILE` | Path to raw digest bytes | +| `PSIGN_ARTIFACT_SIGNING_REST_SIGNATURE_ALGORITHM` | Optional (default API/`RS256`) | Authentication (**one** path): | Variable | Purpose | |----------|---------| -| `SIGNTOOL_RS_ARTIFACT_SIGNING_REST_ACCESS_TOKEN` | Bearer token for **`https://codesigning.azure.net/.default`** | -| `SIGNTOOL_RS_ARTIFACT_SIGNING_REST_MANAGED_IDENTITY` | Set to **`1`** / **`true`** / **`yes`** for IMDS (VMs/containers) | -| `SIGNTOOL_RS_ARTIFACT_SIGNING_REST_TENANT_ID` | With client credentials | -| `SIGNTOOL_RS_ARTIFACT_SIGNING_REST_CLIENT_ID` | With client credentials | -| `SIGNTOOL_RS_ARTIFACT_SIGNING_REST_CLIENT_SECRET` | With client credentials | +| `PSIGN_ARTIFACT_SIGNING_REST_ACCESS_TOKEN` | Bearer token for **`https://codesigning.azure.net/.default`** | +| `PSIGN_ARTIFACT_SIGNING_REST_MANAGED_IDENTITY` | Set to **`1`** / **`true`** / **`yes`** for IMDS (VMs/containers) | +| `PSIGN_ARTIFACT_SIGNING_REST_TENANT_ID` | With client credentials | +| `PSIGN_ARTIFACT_SIGNING_REST_CLIENT_ID` | With client credentials | +| `PSIGN_ARTIFACT_SIGNING_REST_CLIENT_SECRET` | With client credentials | diff --git a/docs/migration-azuresigntool.md b/docs/migration-azuresigntool.md index 483e24c..a94dce3 100644 --- a/docs/migration-azuresigntool.md +++ b/docs/migration-azuresigntool.md @@ -1,21 +1,21 @@ # Migrating from AzureSignTool -This project can replace **AzureSignTool** for Windows signing when built with **`--features azure-kv-sign`**. **`psign-tool-portable`** covers digest checks, verification, and (with **`--features azure-kv-sign-portable`**) the Key Vault **`keys/sign`** step on **digest files** — not full **`sign`** / embed (that stays **`psign-tool-windows`**). +This project can replace **AzureSignTool** for Windows signing when built with **`--features azure-kv-sign`**. **`psign-tool portable`** covers digest checks, verification, and (with **`--features azure-kv-sign-portable`**) the Key Vault **`keys/sign`** step on **digest files** — not full **`sign`** / embed (that stays **`psign-tool`**). **Azure Artifact Signing (Trusted Signing)** via Microsoft’s decoupled **`Azure.CodeSigning.Dlib.dll`** is **not** the Key Vault path: use **`--dlib`** / **`--trusted-signing-dlib-root`** with **`--dmdf`** only (never mixed with **`--azure-key-vault-url`**). See [`migration-artifact-signing.md`](migration-artifact-signing.md). PowerShell OpenAuthenticode overlap (inspect JSON, REST submit, EKU prefix selection) is summarized in [`psa-interoperability.md`](psa-interoperability.md). -## Signing (`psign-tool-windows`) +## Signing (`psign-tool`) Build: ```text -cargo build -p psign --features azure-kv-sign --release --bin psign-tool-windows +cargo build -p psign --features azure-kv-sign --release --bin psign-tool ``` Typical Azure-shaped invocation: ```text -psign-tool-windows.exe sign ^ +psign-tool.exe sign ^ --azure-key-vault-url https://myvault.vault.azure.net ^ --azure-key-vault-certificate my-cert ^ --azure-key-vault-managed-identity ^ @@ -29,7 +29,7 @@ psign-tool-windows.exe sign ^ ### Flags mapped from AzureSignTool -| AzureSignTool | psign-tool-windows | +| AzureSignTool | psign-tool | |---------------|------------------| | `-kvu` / `--azure-key-vault-url` | Same | | `-kvc` | `--azure-key-vault-certificate` | @@ -67,7 +67,7 @@ AzureSignTool documents HRESULT-style batch exits (`README` **Exit Codes**): Enable the same behavior with: - **`--exit-codes azuresigntool`** (alias `azure`), or -- Environment **`SIGNTOOL_RS_EXIT_CODES=azure`** (or `azuresigntool`). +- Environment **`PSIGN_EXIT_CODES=azure`** (or `azuresigntool`). The helper binary **`psign-azure-sign-tool-compat`** sets the environment default to Azure HRESULT semantics before running the same CLI entry point. @@ -75,10 +75,10 @@ Default **`signtool`** exit codes remain **`0` / `1` / `2`**. ### Linux / CI: Key Vault **`keys/sign`** on a raw digest -Build **`psign-tool-portable`** with **`--features azure-kv-sign-portable`**. Produce **`digest.bin`** with **`pe-digest`** / **`cab-digest`** (**`--encoding raw`**), then: +Build **`psign-tool portable`** with **`--features azure-kv-sign-portable`**. Produce **`digest.bin`** with **`pe-digest`** / **`cab-digest`** (**`--encoding raw`**), then: ```bash -psign-tool-portable azure-key-vault-sign-digest \ +psign-tool portable azure-key-vault-sign-digest \ --azure-key-vault-url https://myvault.vault.azure.net \ --azure-key-vault-certificate my-cert \ --digest-file digest.bin \ @@ -86,24 +86,24 @@ psign-tool-portable azure-key-vault-sign-digest \ --azure-key-vault-managed-identity ``` -Stdout prints **standard base64** signature bytes (no PEM). **`--signature-output PATH`** writes **raw** signature. **ECDSA** certificates use **ES256** / **ES384** / **ES512** automatically (same as **`psign-tool-windows`** KV path). Embedding into a PE/CAB still requires Windows **`SignerSignEx3`** (this repo) or a **future portable CMS `SignedData` builder** that consumes these signature octets. +Stdout prints **standard base64** signature bytes (no PEM). **`--signature-output PATH`** writes **raw** signature. **ECDSA** certificates use **ES256** / **ES384** / **ES512** automatically (same as **`psign-tool`** KV path). Embedding into a PE/CAB still requires Windows **`SignerSignEx3`** (this repo) or a **future portable CMS `SignedData` builder** that consumes these signature octets. **CMS signer digest vs subject (file layout) digest:** **`pe-digest`**, **`cab-digest`**, and the MSI installer fingerprint path behind **`verify-msi`** hash **subject layout** — **not** the **32-byte** **`RS256`** input over **`SignerInfo.signedAttrs`**. AzureSignTool’s **`CryptMsg`** path signs **authenticated attributes**; for **RSA SHA-256** the Key Vault **`RS256`** **`value`** is **SHA-256** over that attribute **`SET`** — on PE use **`pe-signer-rs256-prehash --encoding raw`** (**`--index`** = PKCS#7 row, **`--signer-index`** = **`SignerInfo`**); on signed **`.cab`** use **`cab-signer-rs256-prehash`** (or **`extract-cab-pkcs7`** then **`pkcs7-signer-rs256-prehash`**); on **`.msi`** use **`msi-signer-rs256-prehash`** (or **`extract-msi-pkcs7`** then **`pkcs7-signer-rs256-prehash`**); on raw PKCS#7 **`.cat`** bodies use **`catalog-signer-rs256-prehash`** (same bytes as **`pkcs7-signer-rs256-prehash`**). If PKCS#7 is already in a file, use **`pkcs7-signer-rs256-prehash --signer-index N --encoding raw`**. Library parity is tested in **`psign-sip-digest`** (`rsa_pkcs1v15_signed_attrs_verify`). -**Experimental (Linux PE layout only):** **`psign-tool-portable append-pe-pkcs7`** appends PKCS#7 DER as a new **`WIN_CERTIFICATE`** row and recomputes **`CheckSum`**. Use **`pe-checksum --strict`** on the output to gate ImageHlp-style checksum parity. This **does not** assemble PKCS#7 from KV signature bytes — it is for tooling / prototypes until portable **`SignedData`** encode lands. +**Experimental (Linux PE layout only):** **`psign-tool portable append-pe-pkcs7`** appends PKCS#7 DER as a new **`WIN_CERTIFICATE`** row and recomputes **`CheckSum`**. Use **`pe-checksum --strict`** on the output to gate ImageHlp-style checksum parity. This **does not** assemble PKCS#7 from KV signature bytes — it is for tooling / prototypes until portable **`SignedData`** encode lands. -## Verification with **`psign-tool-portable`** +## Verification with **`psign-tool portable`** AzureSignTool does not verify signatures. After signing on Windows, use portable verification on Linux/macOS CI agents where helpful, for example: ```text -psign-tool-portable verify-pe -- +psign-tool portable verify-pe -- ``` For **trust** validation with **explicit anchors** (no OS certificate store), use **`trust-verify-pe`** (or format-specific **`trust-verify-*`** commands). Short-lived signing certificates—common with Artifact Signing profiles—**need RFC3161 timestamping** at sign time so signatures remain verifiable after the leaf expires; combine digest checks with timestamp-aware trust options when applicable: ```text -psign-tool-portable trust-verify-pe ./artifact.exe \ +psign-tool portable trust-verify-pe ./artifact.exe \ --prefer-timestamp-signing-time \ --require-valid-timestamp \ --anchor-dir ./anchors \ @@ -116,4 +116,4 @@ Use the appropriate portable subcommands for your format (`verify-pe`, catalog c ## Integration testing -Exercise the Key Vault path against a real vault (managed identity or client secret), then compare **`psign-tool-windows verify`** and **`psign-tool-portable`** results with a known-good AzureSignTool-signed artifact. +Exercise the Key Vault path against a real vault (managed identity or client secret), then compare **`psign-tool verify`** and **`psign-tool portable`** results with a known-good AzureSignTool-signed artifact. diff --git a/docs/parity-matrix.md b/docs/parity-matrix.md index 520afd2..076615c 100644 --- a/docs/parity-matrix.md +++ b/docs/parity-matrix.md @@ -1,6 +1,6 @@ # Parity Matrix -Product-oriented **tool comparison** (native SignTool vs Azure vs **`psign-tool-windows`** / **`psign-tool-portable`**): [`gap-analysis-signing-platforms.md`](gap-analysis-signing-platforms.md). +Product-oriented **tool comparison** (native SignTool vs Azure vs **`psign-tool`** / **`psign-tool portable`**): [`gap-analysis-signing-platforms.md`](gap-analysis-signing-platforms.md). `Status` values: - `done`: implemented in Rust and parity-tested. @@ -47,11 +47,11 @@ Product-oriented **tool comparison** (native SignTool vs Azure vs **`psign-tool- | Area | Status | Evidence | |------|--------|----------| | PE Authenticode image digest (SHA-256 golden vectors) | done | Upstream `tiny32.signed.efi` / `tiny64.signed.efi` in `tests/fixtures/pe-authenticode-upstream/`; unit tests in `crates/psign-sip-digest/src/pe_digest.rs`; integration in `tests/sip_rust_pe.rs`; matches **`imagehlp.dll`** **`ImageGetDigestStream`** layout via **`authenticode-rs`** — PKCS#7 side wired via **`WINTRUST`** (**`WVTAsn1SpcPeImageData*`** in **`WINTRUST.dll`**) | -| Post-sign digest gate (`--rust-sip pe`, `SIGNTOOL_RS_RUST_SIP=pe`) | partial | Runs after `SignerSignEx3` in `src/win/sign.rs`; PKCS#7 encode/embed still OS SIP | +| Post-sign digest gate (`--rust-sip pe`, `PSIGN_RUST_SIP=pe`) | partial | Runs after `SignerSignEx3` in `src/win/sign.rs`; PKCS#7 encode/embed still OS SIP | | Verify add-on (`--rust-sip-pe-digest-check`) | partial | After embedded WinTrust success in `src/win/verify.rs` | -| PKCS#7 builder / WIN_CERTIFICATE embed in Rust | partial | `pkcs7.rs`: **`parse_pkcs7_signed_data_der`**, **`cms_digest_encapsulated_econtent_bytes`** / **`signer_info_pkcs9_message_digest_octets`** / **`signer_info_signed_attributes_sequence_der`** / **`signed_attributes_replace_pkcs9_message_digest`** / **`signer_info_sha256_digest_over_signed_attrs`** / **`signer_info_clone_with_signed_attrs`** / **`signer_info_clone_with_signature_octets`** (RFC 5652 §5.4 inputs + PKCS#9 refresh + **SHA-256** staging digest + **`SignerInfo`** mutation; fixture parity + **RSA** PKCS#1 v1.5 **RS256** prehash vs **`SignerInfo.signature`** — **`rsa_pkcs1v15_signed_attrs_verify`**), **`signed_data_replace_encapsulated_spc_indirect`**, **`signed_data_replace_signer_info_at`** / **`signed_data_replace_first_signer_info`**, parse/replace **`SpcIndirectDataContent`**, **`encode_pkcs7_content_info_signed_data_der`** (re-encode **`SignedData`**; encap swap + append + **`verify-pe`** mismatch regression tested) + SIP digest checks; **`pe_embed.rs`**: **`wrap_*`**, **`pe_append_*`**, **`pe_compute_image_checksum`** / **`pe_write_image_checksum`** (ImageHlp-style; **`tiny32`**/**`tiny64`** header parity tests); **`pkcs7_wire`**: **`pkcs7_outer_sequence_prefix`**; **`psign-tool-portable`** extract/list/inspect/**`pe-signer-rs256-prehash`**/**`append-pe-pkcs7`**; **new** **`SignerInfo`** / digest-binding CMS production + full sign pipeline still todo; architecture in `docs/rust-sip-architecture.md` | +| PKCS#7 builder / WIN_CERTIFICATE embed in Rust | partial | `pkcs7.rs`: **`parse_pkcs7_signed_data_der`**, **`cms_digest_encapsulated_econtent_bytes`** / **`signer_info_pkcs9_message_digest_octets`** / **`signer_info_signed_attributes_sequence_der`** / **`signed_attributes_replace_pkcs9_message_digest`** / **`signer_info_sha256_digest_over_signed_attrs`** / **`signer_info_clone_with_signed_attrs`** / **`signer_info_clone_with_signature_octets`** (RFC 5652 §5.4 inputs + PKCS#9 refresh + **SHA-256** staging digest + **`SignerInfo`** mutation; fixture parity + **RSA** PKCS#1 v1.5 **RS256** prehash vs **`SignerInfo.signature`** — **`rsa_pkcs1v15_signed_attrs_verify`**), **`signed_data_replace_encapsulated_spc_indirect`**, **`signed_data_replace_signer_info_at`** / **`signed_data_replace_first_signer_info`**, parse/replace **`SpcIndirectDataContent`**, **`encode_pkcs7_content_info_signed_data_der`** (re-encode **`SignedData`**; encap swap + append + **`verify-pe`** mismatch regression tested) + SIP digest checks; **`pe_embed.rs`**: **`wrap_*`**, **`pe_append_*`**, **`pe_compute_image_checksum`** / **`pe_write_image_checksum`** (ImageHlp-style; **`tiny32`**/**`tiny64`** header parity tests); **`pkcs7_wire`**: **`pkcs7_outer_sequence_prefix`**; **`psign-tool portable`** extract/list/inspect/**`pe-signer-rs256-prehash`**/**`append-pe-pkcs7`**; **new** **`SignerInfo`** / digest-binding CMS production + full sign pipeline still todo; architecture in `docs/rust-sip-architecture.md` | | Tier 1b RFC3161 countersignature construction | todo | Stub `crates/psign-sip-digest/src/timestamp.rs`; timestamp **verification** uses Win32 paths in `psign` | -| Tier 1c PE page hashes (`/ph`) | partial | Portable **CMS + table parse + experimental contiguous verify**: `page_hashes.rs`, `psign-tool-portable verify-pe-page-hashes` (differs from WinTrust exclusions); strict `/ph` still `verify --verify-page-hashes` + `WinVerifyTrust` | +| Tier 1c PE page hashes (`/ph`) | partial | Portable **CMS + table parse + experimental contiguous verify**: `page_hashes.rs`, `psign-tool portable verify-pe-page-hashes` (differs from WinTrust exclusions); strict `/ph` still `verify --verify-page-hashes` + `WinVerifyTrust` | | Rust SIP script digest (PowerShell + WSH) | partial | `ps_script.rs`, `wsh_script.rs`, `--rust-sip script`; COM `ConvertTextToUnicode` vs UTF-8/BOM heuristic may diverge on some files | | Rust SIP MSI digest (OLE compound) | partial | `msi_digest.rs`, `--rust-sip msi`, `verify --rust-sip-msi-digest-check`; matches Signify `SignedMsiFile` traversal over `cfb`; PKCS#7 production/embed remains OS SIP | | Rust SIP WIM/ESD digest | partial | `esd_digest.rs`, `--rust-sip esd`, `verify --rust-sip-esd-digest-check`; prefix hash per `EsdSip.dll` (`GetHashDataOffset`, PKCS#7 tail at header **0xBC**); PKCS#7 production/embed remains OS SIP | @@ -75,23 +75,23 @@ Machine-checked mapping of native switches to Rust flags lives in [`psign-cli-ma | Valid signed PE verifies (`/pa`) | `signtool verify /pa ` | Success exit code + trust success | done | | Verify with `/o ` under `/pa` (embedded) | `signtool verify /pa /o … ` | Same exit code as rust `--os-version-check` without `--catalog`/`--catalog-search` (recent kits reject `/o` unless `/a`/`/c`/…) | done (`verify_pa_os_version_check_exit_match`) | | Verify `@rsp` UTF-16 LE + BOM | `signtool @rsp` vs `psign @rsp` | Rust decodes UTF-16 in `response_argv.rs`; native often misparses lines → `documented_native_utf16_rsp_gap` when native exits 1 and rust 0 | partial (intentional; native limitation) | -| Catalog verify + `--os-version-check` | `psign-tool-windows verify --catalog --os-version-check …` | Optional when `SIGNTOOL_RS_CATALOG_*` set; `artifact_catalog_os_version_semantic` in `run-parity-diff.ps1` | done (optional fixture) | +| Catalog verify + `--os-version-check` | `psign-tool verify --catalog --os-version-check …` | Optional when `PSIGN_CATALOG_*` set; `artifact_catalog_os_version_semantic` in `run-parity-diff.ps1` | done (optional fixture) | | Default policy verify failure | `signtool verify ` | Verification error | done | | Sign with PFX + SHA256 | `signtool sign /f /fd SHA256 ` | Signature embedded | done | | Timestamp existing signature | `signtool timestamp /tr /td SHA256 ` | Timestamp countersignature | done | | PowerShell `.ps1` / `.psm1` / `.psd1` description (`/d` `/du`) | Sign then `verify /pa /v /d` | Same Description / Description URL lines as `--print-description` | done (`artifact_verify_ps1_*`, `artifact_verify_psm1_*`, `artifact_verify_psd1_*` in `run-parity-diff.ps1` when PFX + fixtures) | | WSH `.js` / `.vbs` / `.wsf` description (`/d` `/du`) | Same | Same | done (`artifact_verify_js_*`, `artifact_verify_vbs_*`, `artifact_verify_wsf_*`) | -| MSIX `.msix` (etc.) description + RFC3161 sign | Sign `/d` `/du` `/tr` … then `verify /v /d` | Rust `--description` / `--description-url` matches native lines | done (`artifact_verify_msix_print_description_match` when `SIGNTOOL_RS_MSIX_*` secrets set) | -| MSI `.msi` sign + `/pa` verify + description | Same as PE/ps1 pattern on user-supplied unsigned MSI | Optional env `SIGNTOOL_RS_MSI_UNSIGNED_FIXTURE` + PFX; scenarios `sign_msi_*`, `verify_msi_*`, `artifact_verify_msi_print_description_match` | done (optional fixture) | -| WinMD `.winmd` sign + `/pa` verify + description | Native `signtool sign /fd SHA256 /f …` vs rust | CI: `pack-minimal-winmd.ps1` copies the unsigned PE as `.winmd`; optional local `SIGNTOOL_RS_WINMD_UNSIGNED_FIXTURE` + PFX; `sign_winmd_*`, `artifact_verify_winmd_print_description_match`; smoke `scripts/sip-format-smoke.ps1` | done | +| MSIX `.msix` (etc.) description + RFC3161 sign | Sign `/d` `/du` `/tr` … then `verify /v /d` | Rust `--description` / `--description-url` matches native lines | done (`artifact_verify_msix_print_description_match` when `PSIGN_MSIX_*` secrets set) | +| MSI `.msi` sign + `/pa` verify + description | Same as PE/ps1 pattern on user-supplied unsigned MSI | Optional env `PSIGN_MSI_UNSIGNED_FIXTURE` + PFX; scenarios `sign_msi_*`, `verify_msi_*`, `artifact_verify_msi_print_description_match` | done (optional fixture) | +| WinMD `.winmd` sign + `/pa` verify + description | Native `signtool sign /fd SHA256 /f …` vs rust | CI: `pack-minimal-winmd.ps1` copies the unsigned PE as `.winmd`; optional local `PSIGN_WINMD_UNSIGNED_FIXTURE` + PFX; `sign_winmd_*`, `artifact_verify_winmd_print_description_match`; smoke `scripts/sip-format-smoke.ps1` | done | | Rust SIP PE digest gate | `psign sign … --rust-sip pe` vs native sign exit code | Optional fixtures + `scripts/rust-sip-parity-pe.ps1`; corpus `rust_sip_pe_sign_digest_gate_optional` | partial (experimental) | -| Rust SIP verify digest add-on | `psign-tool-windows verify /pa --rust-sip-pe-digest-check` | Ignored test `tests/sip_rust_pe.rs` with `SIGNTOOL_RS_SIGNED_FIXTURE` | partial (experimental) | +| Rust SIP verify digest add-on | `psign-tool verify /pa --rust-sip-pe-digest-check` | Ignored test `tests/sip_rust_pe.rs` with `PSIGN_SIGNED_FIXTURE` | partial (experimental) | | Rust SIP script digest gate | `psign sign … --rust-sip script` on `.ps1`/`.js`/… | Optional ignored parity tests in `tests/parity_signtool.rs` when PFX + fixtures | partial (experimental) | -| Rust SIP script verify add-on | `psign-tool-windows verify /pa --rust-sip-script-digest-check` | Same fixtures as above | partial (experimental) | -| Rust SIP MSI digest gate | `psign sign … --rust-sip msi` on `.msi` | Post-sign `sip_rust::msi_digest` vs PKCS#7 after `SignerSignEx3`; optional `SIGNTOOL_RS_MSI_UNSIGNED_FIXTURE` parity | partial (experimental) | -| Rust SIP MSI verify add-on | `psign-tool-windows verify /pa --rust-sip-msi-digest-check` on `.msi` | After WinTrust success, compares Rust MSI fingerprint vs indirect digest + `MsiDigitalSignatureEx` when present | partial (experimental) | +| Rust SIP script verify add-on | `psign-tool verify /pa --rust-sip-script-digest-check` | Same fixtures as above | partial (experimental) | +| Rust SIP MSI digest gate | `psign sign … --rust-sip msi` on `.msi` | Post-sign `sip_rust::msi_digest` vs PKCS#7 after `SignerSignEx3`; optional `PSIGN_MSI_UNSIGNED_FIXTURE` parity | partial (experimental) | +| Rust SIP MSI verify add-on | `psign-tool verify /pa --rust-sip-msi-digest-check` on `.msi` | After WinTrust success, compares Rust MSI fingerprint vs indirect digest + `MsiDigitalSignatureEx` when present | partial (experimental) | | Rust SIP WIM/ESD digest gate | `psign sign … --rust-sip esd` on `.wim`/`.esd` | Post-sign `sip_rust::esd_digest` vs PKCS#7 after `SignerSignEx3` | partial (experimental) | -| Rust SIP WIM/ESD verify add-on | `psign-tool-windows verify /pa --rust-sip-esd-digest-check` | After WinTrust success on WIM/ESD, compares Rust prefix digest vs PKCS#7 indirect digest | partial (experimental) | +| Rust SIP WIM/ESD verify add-on | `psign-tool verify /pa --rust-sip-esd-digest-check` | After WinTrust success on WIM/ESD, compares Rust prefix digest vs PKCS#7 indirect digest | partial (experimental) | ## Artifact status diff --git a/docs/plan-openauthenticode-parity.md b/docs/plan-openauthenticode-parity.md index da2729c..3f20987 100644 --- a/docs/plan-openauthenticode-parity.md +++ b/docs/plan-openauthenticode-parity.md @@ -6,9 +6,9 @@ This document lists **capabilities found there that psign does not fully mirror ## Already broadly covered here -- **PE, PowerShell-class scripts, WSH `.js`/`.vbs`/`.wsf`**, **MOF / PS1XML-style markers** — Windows signing uses `SignerSignEx3` + SIPs; portable digest checks live in `psign-sip-digest` / `psign-tool-portable`. +- **PE, PowerShell-class scripts, WSH `.js`/`.vbs`/`.wsf`**, **MOF / PS1XML-style markers** — Windows signing uses `SignerSignEx3` + SIPs; portable digest checks live in `psign-sip-digest` / `psign-tool portable`. - **MSIX / APPX** — Windows path + portable ZIP digest verification (PSA uses a pure ZIP/CMS provider; we rely on the Windows SIP for signing). -- **Azure Key Vault–backed signing** — `psign-tool-windows` + `--features azure-kv-sign` (RSA PKCS#1 v1.5 over digest). +- **Azure Key Vault–backed signing** — `psign-tool` + `--features azure-kv-sign` (RSA PKCS#1 v1.5 over digest). - **Artifact Signing via Microsoft dlib** — `--dlib` / `--trusted-signing-dlib-root` + `--dmdf` (Windows only). - **Append signature** — `--append-signature` exists; **parity** with PSA’s nested PKCS#7 attribute (`1.3.6.1.4.1.311.2.4.1`) should be validated with fixtures. - **Remove / clear signature** — `remove` (and related flows) vs PSA `Clear-OpenAuthenticodeSignature`. @@ -38,8 +38,8 @@ This document lists **capabilities found there that psign does not fully mirror **Plan:** -- Add a **`psign-tool-windows inspect-signature`** (or extend **`verify --dump`** if present) that prints **machine-readable** JSON: signer count, nested blobs, digest OID, **timestamp kind** (legacy vs RFC3161), signing time hints, leaf subject/thumbprint (no PowerShell dependency). -- Share parsing logic with **`psign-tool-portable`** where possible (reuse `picky` / existing PKCS#7 helpers from trust crate). +- Add a **`psign-tool inspect-signature`** (or extend **`verify --dump`** if present) that prints **machine-readable** JSON: signer count, nested blobs, digest OID, **timestamp kind** (legacy vs RFC3161), signing time hints, leaf subject/thumbprint (no PowerShell dependency). +- Share parsing logic with **`psign-tool portable`** where possible (reuse `picky` / existing PKCS#7 helpers from trust crate). - Cross-test against PSA output on shared fixtures. ### 3. Verification semantics: timestamp-grace and optional custom trust store @@ -87,7 +87,7 @@ This document lists **capabilities found there that psign does not fully mirror **psign:** Uses **`SignerTimeStampEx3`** / crypto API. -**Plan:** Low priority **integration test** or **`psign-tool-portable`** dev-only tool that speaks RFC3161 HTTP to compare tokens with Windows timestamp pipeline—only if timestamp discrepancies appear in parity reports. +**Plan:** Low priority **integration test** or **`psign-tool portable`** dev-only tool that speaks RFC3161 HTTP to compare tokens with Windows timestamp pipeline—only if timestamp discrepancies appear in parity reports. ## Suggested phase order diff --git a/docs/psa-interoperability.md b/docs/psa-interoperability.md index f2ed31f..e6c9682 100644 --- a/docs/psa-interoperability.md +++ b/docs/psa-interoperability.md @@ -1,6 +1,6 @@ # Interoperability with PowerShell OpenAuthenticode -[PowerShell OpenAuthenticode](https://github.com/jborean93/PowerShell-OpenAuthenticode) (**PSA**) uses portable **.NET CMS** (`SignedCode`) for signing and verification. **`psign`** uses **`SignerSignEx3`** on Windows and **`psign-tool-portable`** / **`psign-authenticode-trust`** for digest + optional anchor trust off-Windows. +[PowerShell OpenAuthenticode](https://github.com/jborean93/PowerShell-OpenAuthenticode) (**PSA**) uses portable **.NET CMS** (`SignedCode`) for signing and verification. **`psign`** uses **`SignerSignEx3`** on Windows and **`psign-tool portable`** / **`psign-authenticode-trust`** for digest + optional anchor trust off-Windows. This note maps PSA behaviors to this repo; see also [`plan-openauthenticode-parity.md`](plan-openauthenticode-parity.md). @@ -8,10 +8,10 @@ This note maps PSA behaviors to this repo; see also [`plan-openauthenticode-pari | PSA | psign / portable | |-----|-------------------------| -| **`Get-OpenAuthenticodeSignature`** (nested PKCS#7 walk + timestamp OIDs) | **`psign-tool-portable inspect-authenticode`** or **`psign-tool-windows inspect-signature`** — JSON from **`psign-authenticode-trust`** (`inspect_pe_authenticode`, `inspect_authenticode_pkcs7_der`) | -| **`-SkipCertificateCheck`** (CMS signature check without full chain policy) | **`psign-tool-portable trust-verify-*`** with **`--allow-loose-signing-cert`** (`AuthenticodeTrustPolicy::ignore_signing_certificate_check`) plus explicit **`--anchor-dir`** / **`--authroot-cab`** | +| **`Get-OpenAuthenticodeSignature`** (nested PKCS#7 walk + timestamp OIDs) | **`psign-tool portable inspect-authenticode`** or **`psign-tool inspect-signature`** — JSON from **`psign-authenticode-trust`** (`inspect_pe_authenticode`, `inspect_authenticode_pkcs7_der`) | +| **`-SkipCertificateCheck`** (CMS signature check without full chain policy) | **`psign-tool portable trust-verify-*`** with **`--allow-loose-signing-cert`** (`AuthenticodeTrustPolicy::ignore_signing_certificate_check`) plus explicit **`--anchor-dir`** / **`--authroot-cab`** | | Timestamp-aware validity for **expired leaf** certs | **`--prefer-timestamp-signing-time`**, **`--require-valid-timestamp`**, **`--as-of`** on **`trust-verify-*`** | -| No revocation in PSA README | Portable trust path does **not** aim to replace OS revocation; **`psign-tool-windows verify`** follows **`WinVerifyTrust`** | +| No revocation in PSA README | Portable trust path does **not** aim to replace OS revocation; **`psign-tool verify`** follows **`WinVerifyTrust`** | ## Signing @@ -23,4 +23,4 @@ This note maps PSA behaviors to this repo; see also [`plan-openauthenticode-pari ## Appendix signatures -PSA **`Add-OpenAuthenticodeSignature`** nests PKCS#7 under OID **`1.3.6.1.4.1.311.2.4.1`**. **`psign-tool-windows sign --append-signature`** follows the same **`SIG_APPEND`**/`SignerSignEx3` behavior; the parity test **`append_signature_pe_nested_pkcs7_visible_to_inspector`** (ignored; requires fixtures) asserts nested blobs appear in **`inspect-pe`** JSON output. +PSA **`Add-OpenAuthenticodeSignature`** nests PKCS#7 under OID **`1.3.6.1.4.1.311.2.4.1`**. **`psign-tool sign --append-signature`** follows the same **`SIG_APPEND`**/`SignerSignEx3` behavior; the parity test **`append_signature_pe_nested_pkcs7_visible_to_inspector`** (ignored; requires fixtures) asserts nested blobs appear in **`inspect-pe`** JSON output. diff --git a/docs/psign-cli-matrix.json b/docs/psign-cli-matrix.json index 5592076..74f482d 100644 --- a/docs/psign-cli-matrix.json +++ b/docs/psign-cli-matrix.json @@ -1,5 +1,5 @@ { - "native_style_cli_aliases": "On Windows, argv tokens starting with `/` after the subcommand are rewritten to GNU-style flags before clap parses (e.g. `verify /pa /q file.exe`, `verify /vr`, `verify /p7s path.p7s`, `verify /testroot`, `verify /sl`, `verify /ph` pairs with `/v` like native, `sign /fd SHA256 /tseal URL file`, `sign /sa OID value`, `sign /seal`, `sign /c TemplateName`, `timestamp /force`, `remove /s file.exe`, `rdp /sha256 HASH /l file.rdp`). Additionally, psign-tool-windows accepts `--` long aliases mirroring native names (sign `--fd`, `--tr`, `--tseal`, `--c`, `--sa`, `--fdchw`, `--tdchw`, `--rmc`, `--seal`, `--itos`, `--force`, `--nosealwarn`, `--noenclavewarn`; verify `--tw`, `--kp`, `--vr`, `--sha1`, `--p7s`, `--testroot`, `--sl`, `--bp`, `--enclave`; timestamp `--tr`/`--td`/`--tp`, `--force`, `--nosealwarn`; catdb `--d`/`--g`/`--r`/`--u`; remove `--s`/`--c`/`--u`; rdp `--sha256`, `--sha1`, `--l`).", + "native_style_cli_aliases": "On Windows, argv tokens starting with `/` after the subcommand are rewritten to GNU-style flags before clap parses (e.g. `verify /pa /q file.exe`, `verify /vr`, `verify /p7s path.p7s`, `verify /testroot`, `verify /sl`, `verify /ph` pairs with `/v` like native, `sign /fd SHA256 /tseal URL file`, `sign /sa OID value`, `sign /seal`, `sign /c TemplateName`, `timestamp /force`, `remove /s file.exe`, `rdp /sha256 HASH /l file.rdp`). Additionally, psign-tool accepts `--` long aliases mirroring native names (sign `--fd`, `--tr`, `--tseal`, `--c`, `--sa`, `--fdchw`, `--tdchw`, `--rmc`, `--seal`, `--itos`, `--force`, `--nosealwarn`, `--noenclavewarn`; verify `--tw`, `--kp`, `--vr`, `--sha1`, `--p7s`, `--testroot`, `--sl`, `--bp`, `--enclave`; timestamp `--tr`/`--td`/`--tp`, `--force`, `--nosealwarn`; catdb `--d`/`--g`/`--r`/`--u`; remove `--s`/`--c`/`--u`; rdp `--sha256`, `--sha1`, `--l`).", "invocation": [ {"native": "@responsefile", "rust": "@responsefile", "tier": "P0", "status": "implemented", "notes": "UTF-8 (optional BOM) or UTF-16 LE/BE with BOM; invalid UTF-8 falls back to UTF-16 LE without BOM; one argument per line; double-quoted lines with \"\" escapes; blank line separates commands when @file is the only tail arg; inline @path splices one block; `@@` prefix strips one `@` for a literal leading at-sign; native signtool may mis-parse @ when the signtool.exe path contains spaces—parity script uses a TEMP copy"} ], @@ -56,8 +56,8 @@ {"native": "/tseal", "rust": "--seal-timestamp-url (--tseal)", "tier": "P0", "status": "implemented", "notes": "Sign-time sealed RFC3161 URL; mutually exclusive with /tr and /t; same SignerSignEx3 RFC3161 path as /tr here"}, {"native": "/u", "rust": "--eku-oid", "tier": "P0", "status": "implemented"}, {"native": "/uw", "rust": "--eku-windows-system-component", "tier": "P0", "status": "implemented"}, - {"native": "(experimental)", "rust": "--rust-sip pe|off", "tier": "P2", "status": "partial", "notes": "Post-sign PE Authenticode digest vs PKCS#7 indirect digest after `SignerSignEx3`; `SIGNTOOL_RS_RUST_SIP=pe`; `/rust-sip pe` native argv; not production SIP replacement"}, - {"native": "(experimental env)", "rust": "SIGNTOOL_RS_RUST_SIP", "tier": "P2", "status": "partial", "notes": "`pe` / `script` / `msi` / `esd` / `msix` / `cab` / `catalog` enable digest gates; `off` disables env; `--rust-sip off` overrides"}, + {"native": "(experimental)", "rust": "--rust-sip pe|off", "tier": "P2", "status": "partial", "notes": "Post-sign PE Authenticode digest vs PKCS#7 indirect digest after `SignerSignEx3`; `PSIGN_RUST_SIP=pe`; `/rust-sip pe` native argv; not production SIP replacement"}, + {"native": "(experimental env)", "rust": "PSIGN_RUST_SIP", "tier": "P2", "status": "partial", "notes": "`pe` / `script` / `msi` / `esd` / `msix` / `cab` / `catalog` enable digest gates; `off` disables env; `--rust-sip off` overrides"}, {"native": "(experimental)", "rust": "--rust-sip script", "tier": "P2", "status": "partial", "notes": "Post-sign script digest vs PKCS#7: PowerShell UTF-16 strip (pwrshsip); WSH strip + offset dword (wshext); COM text decode may differ"}, {"native": "(experimental)", "rust": "--rust-sip msi", "tier": "P2", "status": "partial", "notes": "Post-sign MSI OLE fingerprint vs PKCS#7 indirect digest after `SignerSignEx3` (Signify-compatible `sip_rust::msi_digest`); `/rust-sip msi` argv; only when target resolves to Windows Installer format"}, {"native": "(experimental)", "rust": "--rust-sip esd", "tier": "P2", "status": "partial", "notes": "Post-sign WIM/ESD prefix digest vs PKCS#7 (`sip_rust::esd_digest`, `EsdSip.dll` semantics); `/rust-sip esd` argv; `.wim`/`.esd` only"}, @@ -83,7 +83,7 @@ {"native": "/ms", "rust": "--multiple-semantics", "tier": "P1", "status": "partial", "notes": "Documented no-op; WinVerifyTrust default varies by OS"}, {"native": "/p7", "rust": "--verify-pkcs7-file", "tier": "P0", "status": "partial", "notes": "Same WinTrust file path as PE; PKCS-only semantics limited"}, {"native": "/d", "rust": "--print-description", "tier": "P1", "status": "implemented", "notes": "SPC_SP_OPUS_INFO from WinVerifyTrust state; requires -v like native"}, - {"native": "/ph", "rust": "--verify-page-hashes", "tier": "P1", "status": "implemented", "notes": "Requires -v like native; WTD_HASH_ONLY_FLAG + OID scan for SPC_PE_IMAGE_PAGE_HASHES; warns exit 2 if absent; portable probe + table parse (no WinTrust): `psign-tool-portable pe-has-page-hashes` / `pe-page-hash-info` — see `portable_digest_cli`"}, + {"native": "/ph", "rust": "--verify-page-hashes", "tier": "P1", "status": "implemented", "notes": "Requires -v like native; WTD_HASH_ONLY_FLAG + OID scan for SPC_PE_IMAGE_PAGE_HASHES; warns exit 2 if absent; portable probe + table parse (no WinTrust): `psign-tool portable pe-has-page-hashes` / `pe-page-hash-info` — see `portable_digest_cli`"}, {"native": "/r", "rust": "--chain-root-subject", "tier": "P0", "status": "implemented"}, {"native": "/sha1", "rust": "--signer-thumbprint-sha1", "tier": "P0", "status": "implemented", "notes": "Repeatable; signer cert SHA1 must match one value"}, {"native": "/ca", "rust": "--intermediate-ca-sha1", "tier": "P0", "status": "implemented", "notes": "Repeatable; at least one intermediate CA thumbprint must match chain"}, @@ -146,60 +146,60 @@ "extensions": [".ps1", ".psm1", ".psd1"], "windows_mechanism": "Registered Cryptography SIP for PowerShell / module / manifest files; native `signtool` and `SignerSignEx3` load the OS SIP implementation.", "rust_path": "Same Win32 stack (`SignerSignEx3`, `WinVerifyTrust`) — uses the OS SIP DLL; see `src/win/code_sign_format.rs`.", - "parity": "`scripts/run-parity-diff.ps1`: `.ps1` — `sign_ps1_*`, `verify_ps1_*`, `artifact_verify_ps1_print_description_match` (`/d` `/du` + `verify /v /d` vs `--print-description`; shares `$parityDesc`/`$parityUrl` with PE). `.psm1` / `.psd1` — same plus `artifact_verify_psm1_print_description_match`, `artifact_verify_psd1_print_description_match`. Fixtures under `tests/fixtures/` or env overrides `SIGNTOOL_RS_PS1_UNSIGNED_FIXTURE`, `_PSM1_`, `_PSD1_`. Exact SHA256 when PKCS#7 matches native; else `artifact_semantic_match` if both `/pa` verify succeed." + "parity": "`scripts/run-parity-diff.ps1`: `.ps1` — `sign_ps1_*`, `verify_ps1_*`, `artifact_verify_ps1_print_description_match` (`/d` `/du` + `verify /v /d` vs `--print-description`; shares `$parityDesc`/`$parityUrl` with PE). `.psm1` / `.psd1` — same plus `artifact_verify_psm1_print_description_match`, `artifact_verify_psd1_print_description_match`. Fixtures under `tests/fixtures/` or env overrides `PSIGN_PS1_UNSIGNED_FIXTURE`, `_PSM1_`, `_PSD1_`. Exact SHA256 when PKCS#7 matches native; else `artifact_semantic_match` if both `/pa` verify succeed." }, { "extensions": [".exe", ".dll", ".sys", ".ocx", ".efi", ".scr", ".cpl", ".mui"], "windows_mechanism": "PE Authenticode (`WIN_CERTIFICATE`); SIP resolves from PE metadata.", "rust_path": "Same Win32 SIP delegation as native; optional experimental `--rust-sip pe` post-sign digest gate (`crates/psign-sip-digest`, re-exported under `sip_rust`).", - "parity": "`run-parity-diff.ps1` `sign_pe_fixture_sha256_match_native` / `verify_pe_fixture_pa_exit_match` on `SIGNTOOL_RS_UNSIGNED_FIXTURE` (two-copy sign + SHA256 or verify-valid semantic match); plus `artifact_sign_two_pe_exit_parity` and semantic verify scenarios." + "parity": "`run-parity-diff.ps1` `sign_pe_fixture_sha256_match_native` / `verify_pe_fixture_pa_exit_match` on `PSIGN_UNSIGNED_FIXTURE` (two-copy sign + SHA256 or verify-valid semantic match); plus `artifact_sign_two_pe_exit_parity` and semantic verify scenarios." }, { "extensions": [".winmd"], "windows_mechanism": "Windows metadata files are CLI assemblies in a PE-based container; Authenticode via OS SIP (same `SignerSignEx3` / `WinVerifyTrust` stack as PE signing).", "rust_path": "`CodeSignFormat::WindowsMetadata` in `src/win/code_sign_format.rs`; same Win32 delegation as native.", - "parity": "Optional `scripts/run-parity-diff.ps1` when `SIGNTOOL_RS_WINMD_UNSIGNED_FIXTURE` + `SIGNTOOL_RS_TEST_PFX` set: `sign_winmd_sha256_match_native`, `verify_winmd_pa_exit_match`, `artifact_verify_winmd_print_description_match`; optional `SIGNTOOL_RS_WINMD_TIMESTAMP_URL`. Local smoke: `scripts/sip-format-smoke.ps1`. `remove /s` allowed only for PE-image-backed mapped extensions — `.winmd` is treated as PE-backed (`Image*` APIs) like `.exe`." + "parity": "Optional `scripts/run-parity-diff.ps1` when `PSIGN_WINMD_UNSIGNED_FIXTURE` + `PSIGN_TEST_PFX` set: `sign_winmd_sha256_match_native`, `verify_winmd_pa_exit_match`, `artifact_verify_winmd_print_description_match`; optional `PSIGN_WINMD_TIMESTAMP_URL`. Local smoke: `scripts/sip-format-smoke.ps1`. `remove /s` allowed only for PE-image-backed mapped extensions — `.winmd` is treated as PE-backed (`Image*` APIs) like `.exe`." }, { "extensions": [".msix", ".msixbundle", ".appx", ".appxbundle"], "windows_mechanism": "AppX/MSIX SIP; tooling often uses decoupled digest `/dlib` + `/dmdf` + `/ph`.", "rust_path": "Same Win32 SIP + decoupled digest bridge as native.", - "parity": "`scripts/msix-parity-sign.ps1` and MSIX blocks in `run-parity-diff.ps1` (`artifact_msix_sign_semantic`, `artifact_msix_decoupled_semantic` when dlib env set). Description parity: `artifact_verify_msix_print_description_match` — sign with `/d` `/du` + RFC3161 (`SIGNTOOL_RS_MSIX_*` env), then `verify /v /d` vs `--print-description` (same strings as PE description test)." + "parity": "`scripts/msix-parity-sign.ps1` and MSIX blocks in `run-parity-diff.ps1` (`artifact_msix_sign_semantic`, `artifact_msix_decoupled_semantic` when dlib env set). Description parity: `artifact_verify_msix_print_description_match` — sign with `/d` `/du` + RFC3161 (`PSIGN_MSIX_*` env), then `verify /v /d` vs `--print-description` (same strings as PE description test)." }, { "extensions": [".msi", ".msp", ".mst"], "windows_mechanism": "Windows Installer SIP (OLE compound storage Authenticode).", "rust_path": "`SignerSignEx3` / `WinVerifyTrust` — OS SIP; `code_sign_format::WindowsInstaller`; experimental digest parity in `sip_rust::msi_digest` (`--rust-sip msi`, `verify --rust-sip-msi-digest-check`); `remove` rejects (not PE `Image*`).", - "parity": "No bundled unsigned `.msi` in-repo (OLE compound); optional parity in `run-parity-diff.ps1` when `SIGNTOOL_RS_MSI_UNSIGNED_FIXTURE` + `SIGNTOOL_RS_TEST_PFX` point at a valid package: `sign_msi_sha256_match_native`, `verify_msi_pa_exit_match`, `artifact_verify_msi_print_description_match`. Optional sign-time RFC3161: `SIGNTOOL_RS_MSI_TIMESTAMP_URL`." + "parity": "No bundled unsigned `.msi` in-repo (OLE compound); optional parity in `run-parity-diff.ps1` when `PSIGN_MSI_UNSIGNED_FIXTURE` + `PSIGN_TEST_PFX` point at a valid package: `sign_msi_sha256_match_native`, `verify_msi_pa_exit_match`, `artifact_verify_msi_print_description_match`. Optional sign-time RFC3161: `PSIGN_MSI_TIMESTAMP_URL`." }, { "extensions": [".wim", ".esd"], "windows_mechanism": "WIM/ESD image SIP (`EsdSip.dll`; GUID `9F3053C5-439D-4BF7-8A77-04F0450A1D9F`).", "rust_path": "`SignerSignEx3` / `WinVerifyTrust` — OS SIP; `code_sign_format::WimImage`; experimental digest parity in `sip_rust::esd_digest` (`--rust-sip esd`, `verify --rust-sip-esd-digest-check`); `remove` rejects.", - "parity": "No bundled signed WIM in-repo; optional local sign/verify with native `signtool` then `psign-tool-windows verify /pa --rust-sip-esd-digest-check` for Rust consistency." + "parity": "No bundled signed WIM in-repo; optional local sign/verify with native `signtool` then `psign-tool verify /pa --rust-sip-esd-digest-check` for Rust consistency." }, { "extensions": [".js", ".vbs", ".wsf"], "windows_mechanism": "Windows Script Host–related SIP where registered for the extension.", "rust_path": "Same Win32 stack as native (`SignerSignEx3`, `WinVerifyTrust`); no in-tree SIP port.", - "parity": "`run-parity-diff.ps1`: per-extension sign/verify SHA256 + `artifact_verify_{js,vbs,wsf}_print_description_match` (`/d` `/du` then `verify /v /d` vs `--print-description`; same `$parityDesc`/`$parityUrl` as PE). Fixtures `unsigned-sample.{js,vbs,wsf}` or env `SIGNTOOL_RS_JS_UNSIGNED_FIXTURE`, `_VBS_`, `_WSF_`. Requires OS SIP registration per extension." + "parity": "`run-parity-diff.ps1`: per-extension sign/verify SHA256 + `artifact_verify_{js,vbs,wsf}_print_description_match` (`/d` `/du` then `verify /v /d` vs `--print-description`; same `$parityDesc`/`$parityUrl` as PE). Fixtures `unsigned-sample.{js,vbs,wsf}` or env `PSIGN_JS_UNSIGNED_FIXTURE`, `_VBS_`, `_WSF_`. Requires OS SIP registration per extension." }, { "extensions": [".rdp"], "windows_mechanism": "`rdpsign.exe` parses RDP settings, signs the UTF-16 secure-settings blob with detached PKCS#7 (`CryptSignMessage`), serializes `{version,type,length,pkcs7}`, and writes `SignScope` / `Signature` records.", - "rust_path": "`psign-tool-windows rdp --sha256 file.rdp` implements the RDP-specific path directly; `psign-tool-portable rdp --cert cert.pem --key key.pem file.rdp` uses the same RDP SignScope/secure-blob serializer and creates detached RSA/SHA-256 CMS without Win32. This is not a SIP/SignerSignEx3 file format.", + "rust_path": "`psign-tool rdp --sha256 file.rdp` implements the RDP-specific path directly; `psign-tool portable rdp --cert cert.pem --key key.pem file.rdp` uses the same RDP SignScope/secure-blob serializer and creates detached RSA/SHA-256 CMS without Win32. This is not a SIP/SignerSignEx3 file format.", "parity": "Unit fixtures under `tests/fixtures/rdp/` cover UTF-8, UTF-8 BOM, UTF-16 LE/BE with and without BOM, partial/stale SignScope/Signature records, malformed records, secure blob generation, serialized signature shape, and a signed `.rdp` generated with the repo test PFX. Portable CLI tests cover external PKCS#7 embedding and local RSA/SHA-256 cert+key signing; Windows tests exercise the same shared fixtures through the `psign` crate and optional integration can compare against native `rdpsign` output shape." } ], "portable_digest_cli": { - "binary": "psign-tool-portable", + "binary": "psign-tool portable", "crate": "crates/psign-digest-cli", "depends_on": "crates/psign-sip-digest; crates/psign-authenticode-trust (trust-verify-* subcommands)", "platforms_note": "Builds on Linux/macOS/Windows; no `windows` / WinVerifyTrust dependency.", "purpose": "Optional digest recomputation vs PKCS#7 indirect data and PE page-hash OID scans — additive to native signtool, not a full replacement.", "commands": [ {"name": "pe-digest", "maps_to_native_concept": "PE SIP image hash (algorithm-selectable)"}, - {"name": "verify-pe", "maps_to_native_concept": "Consistency check after conceptual WinTrust success — similar to `psign-tool-windows verify --rust-sip-pe-digest-check`"}, + {"name": "verify-pe", "maps_to_native_concept": "Consistency check after conceptual WinTrust success — similar to `psign-tool verify --rust-sip-pe-digest-check`"}, {"name": "trust-verify-pe", "maps_to_native_concept": "Explicit-anchor PKCS#7 trust + picky chain + PE digest (not WinTrust); optional `--as-of YYYY-MM-DD` for expired fixtures"}, {"name": "trust-verify-cab", "maps_to_native_concept": "`verify-cab` digest consistency then same trust stack as PE"}, {"name": "trust-verify-catalog", "maps_to_native_concept": "`verify-catalog` CMS digest consistency then picky trust when Authenticode-wrapped"}, diff --git a/docs/psign-cli-matrix.md b/docs/psign-cli-matrix.md index c549f81..7c11760 100644 --- a/docs/psign-cli-matrix.md +++ b/docs/psign-cli-matrix.md @@ -1,6 +1,6 @@ # SignTool CLI parity matrix -This document summarizes native `signtool.exe` options plus the related `rdpsign.exe` RDP signing surface vs the **`psign-tool-windows`** CLI (Rust package **`psign`**). The **machine-readable source of truth** is [`psign-cli-matrix.json`](psign-cli-matrix.json) (`commands.sign`, `commands.verify`, `commands.timestamp`, `commands.catdb`, `commands.remove`, `commands.rdp`, `global_options`, `invocation`, `code_sign_file_formats`). +This document summarizes native `signtool.exe` options plus the related `rdpsign.exe` RDP signing surface vs the **`psign-tool`** CLI (Rust package **`psign`**). The **machine-readable source of truth** is [`psign-cli-matrix.json`](psign-cli-matrix.json) (`commands.sign`, `commands.verify`, `commands.timestamp`, `commands.catdb`, `commands.remove`, `commands.rdp`, `global_options`, `invocation`, `code_sign_file_formats`). SDK help text used for cross-checking can be captured locally under **`parity-output/`** (`signtool-help-*.txt`; gitignored). The pinned kit version is recorded in this repo’s `sdk_kit` field in the JSON (currently aligned with `10.0.26100.0`). @@ -37,7 +37,7 @@ Full native ↔ Rust mappings, tiers, and per-flag notes are **only** maintained - **Verify `/o`**: Catalog WinTrust only — `--os-version-check` sets `WTD_USE_DEFAULT_OSVER_CHECK` in `verify_with_catalog`; embedded verify without `--catalog` / `--catalog-search` / `--catalog-database-guid` errors to match current signtool (see JSON `verify` entry for `/o`). - **Detached PKCS#7**: Implemented with chain policy; bare CMS `SignedData` from `signtool /p7` is normalized to PKCS#7 `ContentInfo` before `CryptVerifyDetachedMessageSignature` (`src/win/verify_detached.rs`). - **Verify `/bp`, `/enclave`**: CLI accepted; explicit not-implemented errors pending published WinTrust action/policy GUIDs (JSON marks partial). -- **RDP signing**: `psign-tool-windows rdp --sha256 file.rdp` ports `rdpsign.exe` by writing native `SignScope` / `Signature` records using detached PKCS#7 over the RDP secure-settings blob. `psign-tool-portable rdp --cert cert.der --key key.pk8 file.rdp` uses the same RDP blob/record logic with portable RSA/SHA-256 CMS creation; fixtures cover UTF-8, UTF-16 with/without BOM, stale/partial signatures, malformed records, and a repo-test-cert signed sample. +- **RDP signing**: `psign-tool rdp --sha256 file.rdp` ports `rdpsign.exe` by writing native `SignScope` / `Signature` records using detached PKCS#7 over the RDP secure-settings blob. `psign-tool portable rdp --cert cert.der --key key.pk8 file.rdp` uses the same RDP blob/record logic with portable RSA/SHA-256 CMS creation; fixtures cover UTF-8, UTF-16 with/without BOM, stale/partial signatures, malformed records, and a repo-test-cert signed sample. ## Gaps intentionally partial diff --git a/docs/roadmap-authenticode-linux.md b/docs/roadmap-authenticode-linux.md index ca3a76d..a342dfd 100644 --- a/docs/roadmap-authenticode-linux.md +++ b/docs/roadmap-authenticode-linux.md @@ -1,24 +1,24 @@ # Roadmap: Authenticode tooling on Linux -The primary `psign` binary is **Windows-first**: it depends on **`windows`**, **WinVerifyTrust**, **SignerSignEx3**, and OS **CryptSIP** registration. A practical Linux story is **phased**: keep Windows as the reference implementation while carving out **portable** pieces. +The primary `psign-tool` binary is unified: Windows mode depends on **`windows`**, **WinVerifyTrust**, **SignerSignEx3**, and OS **CryptSIP** registration, while portable mode uses Rust digest/trust implementations. A practical Linux story is **phased**: keep Windows as the reference implementation while carving out **portable** pieces. **Cross-tool comparison (native signtool vs AzureSignTool vs Artifact Signing vs this repo):** [`gap-analysis-signing-platforms.md`](gap-analysis-signing-platforms.md). **Linux cookbook (verify / REST / hybrid):** [`linux-signing-pipelines.md`](linux-signing-pipelines.md). ## Phase 0 — CI and hygiene - **`ci-unix`**: `cargo fmt --check`, `cargo metadata --locked`, **`cargo clippy`** on portable digest + trust crates + **`psign` lib (`-D warnings`)**, **`cargo test -p psign-sip-digest --lib`**, **`cargo test -p psign-authenticode-trust --lib`**, **`cargo check -p psign`**, **`cargo test -p psign --lib`** (see `.github/workflows/ci-unix.yml`). -- **Cargo aliases** (repo `.cargo/config.toml`): `cargo digest-check` / `cargo digest-test` expand to the portable digest + **`psign-authenticode-trust`** crates (no Windows stub binary required); `cargo unix-lib-check` mirrors **`cargo check -p psign --lib`** (Linux/macOS portable surface); **`cargo windows-bin`** builds the **`psign-tool-windows`** executable (**`-p psign --bin psign-tool-windows`**) because **`default-members`** omit the root crate; **`cargo depgraph`** wraps **`cargo run -p psign --bin psign-depgraph`** so manifest generation works with **`default-members`**. +- **Cargo aliases** (repo `.cargo/config.toml`): `cargo digest-check` / `cargo digest-test` expand to the portable digest + **`psign-authenticode-trust`** crates (no Windows stub binary required); `cargo unix-lib-check` mirrors **`cargo check -p psign --lib`** (Linux/macOS portable surface); **`cargo windows-bin`** builds the **`psign-tool`** executable (**`-p psign --bin psign-tool`**) because **`default-members`** omit the root crate; **`cargo depgraph`** wraps **`cargo run -p psign --bin psign-depgraph`** so manifest generation works with **`default-members`**. - **Workspace `default-members`** (root `Cargo.toml`): a bare **`cargo build`** / **`cargo test`** at the repo root targets **`psign-sip-digest`**, **`psign-digest-cli`**, and **`psign-authenticode-trust`**. Use **`cargo build --workspace`** or **`-p psign`** when you need the main crate on a Windows checkout. - **Local Linux/macOS mirror of CI** (from repo root, `--locked` optional but matches CI): `cargo test -p psign-sip-digest --lib --locked`, `cargo test -p psign-authenticode-trust --lib --locked`, and `cargo test -p psign-digest-cli --locked`, or `cargo digest-test`. - Install the portable CLI only: `cargo install --path crates/psign-digest-cli --locked` (binary name **`psign-tool-portable`**). -- The **`psign`** **CLI binary** on non-Windows is a **stub** that exits with an explanatory message; **`win`** is behind **`#[cfg(windows)]`** so **`windows`** is not a dependency on Linux. + Build the unified CLI: `cargo build -p psign --bin psign-tool --locked`; use **`psign-tool portable ...`** for portable-only diagnostics. +- The **`psign`** **CLI binary** on non-Windows dispatches to portable Rust paths where available; **`win`** is behind **`#[cfg(windows)]`** so **`windows`** is not a dependency on Linux. ## Phase 1 — Workspace split: `psign-sip-digest` (done) - **`crates/psign-sip-digest`** holds portable digest modules (**no `windows` dependency**). The Win32 binary re-exports them from **`src/win/sip_rust/mod.rs`** and keeps thin **`sign_*`** helpers that need **`GlobalOpts`**. - **`ci-unix`** runs **`cargo test -p psign-sip-digest --lib --locked`** (see `.github/workflows/ci-unix.yml`). -- **CLI:** **`psign-tool-portable`** (`crates/psign-digest-cli`, binary name `psign-tool-portable`) — `pe-digest`, `verify-pe` (digest-only PKCS#7 consistency), **`trust-verify-pe`** / **`trust-verify-cab`** / **`trust-verify-catalog`** / **`trust-verify-detached`** (explicit-anchor trust + picky chain), `pe-has-page-hashes`, `pe-page-hash-info`, `verify-pe-page-hashes`, `pe-authenticode-ranges` (Authenticode digest file segments), `verify-cab`, `verify-msi`, `verify-esd`, `verify-msix`, `verify-catalog`, `verify-script`, `cab-digest`, **`extract-cab-pkcs7`**, **`cab-signer-rs256-prehash`**, **`extract-msi-pkcs7`**, **`msi-signer-rs256-prehash`**, **`catalog-signer-rs256-prehash`**, **`inspect-pe-spc-indirect`**, **`extract-pe-pkcs7`**, **`list-pe-pkcs7`**, **`pe-signer-rs256-prehash`** (KV **`RS256`** CMS prehash; **`--signer-index`** for multi-**`SignerInfo`** **`SignedData`**), **`pkcs7-signer-rs256-prehash`**, **`append-pe-pkcs7`** (experimental). Runs on Linux/macOS; does **not** call `WinVerifyTrust`. +- **CLI:** **`psign-tool portable ...`** (runner in `crates/psign-digest-cli`) — `pe-digest`, `verify-pe` (digest-only PKCS#7 consistency), **`trust-verify-pe`** / **`trust-verify-cab`** / **`trust-verify-catalog`** / **`trust-verify-detached`** (explicit-anchor trust + picky chain), `pe-has-page-hashes`, `pe-page-hash-info`, `verify-pe-page-hashes`, `pe-authenticode-ranges` (Authenticode digest file segments), `verify-cab`, `verify-msi`, `verify-esd`, `verify-msix`, `verify-catalog`, `verify-script`, `cab-digest`, **`extract-cab-pkcs7`**, **`cab-signer-rs256-prehash`**, **`extract-msi-pkcs7`**, **`msi-signer-rs256-prehash`**, **`catalog-signer-rs256-prehash`**, **`inspect-pe-spc-indirect`**, **`extract-pe-pkcs7`**, **`list-pe-pkcs7`**, **`pe-signer-rs256-prehash`** (KV **`RS256`** CMS prehash; **`--signer-index`** for multi-**`SignerInfo`** **`SignedData`**), **`pkcs7-signer-rs256-prehash`**, **`append-pe-pkcs7`** (experimental). Runs on Linux/macOS; does **not** call `WinVerifyTrust`. - **Trust library:** **`psign-authenticode-trust`** — see **[authenticode-trust-stack.md](authenticode-trust-stack.md)** and **[authroot-linux-verify.md](authroot-linux-verify.md)**. - Remaining Linux work: optional **revocation**, **PinRules**, and full CryptoAPI policy parity still need dedicated design; digest CLI extras (JSON output, stdin) remain optional. @@ -36,25 +36,25 @@ Order-of-effort sketch (each step needs tests + fixtures): 3. **Additional embedders** — CAB, MSI streams, MSIX ZIP manipulation (hardest: **`AppxSipCreateIndirectData`**-equivalent APPX blob + publisher binding rules). 4. **RFC3161 request/sign** — Replace stub [`timestamp.rs`](../crates/psign-sip-digest/src/timestamp.rs) for post-sign or nested countersignatures. -Until Phase 2 completes, **verify-first Linux CI** remains the supported story; **production signing** stays on **`psign-tool-windows`** (or native **`signtool.exe`**). +Until Phase 2 completes, **verify-first Linux CI** remains the supported story; **production signing** stays on **`psign-tool`** (or native **`signtool.exe`**). ## Phase 3 — Container formats that are OS-agnostic Already aligned in Rust for **cleartext** subjects: -- **MSIX / APPX / bundles** (ZIP layout) — `sip_rust::msix_digest` (encrypted **Eappx** stays out of scope without Windows crypto). **`psign-tool-portable verify-msix`** exercises the same portable ZIP/hash path on Linux; **manifest publisher vs PKCS#7 signer** enforcement remains **`AppxSip`** / **`SignerSignEx*`** on Windows. +- **MSIX / APPX / bundles** (ZIP layout) — `sip_rust::msix_digest` (encrypted **Eappx** stays out of scope without Windows crypto). **`psign-tool portable verify-msix`** exercises the same portable ZIP/hash path on Linux; **manifest publisher vs PKCS#7 signer** enforcement remains **`AppxSip`** / **`SignerSignEx*`** on Windows. - **MSI** OLE tree — `sip_rust::msi_digest`. - **ESD / WIM** prefix hash — `sip_rust::esd_digest`. - **PE / CAB / catalog** digests — pure byte/layout algorithms; **deployment policy** still differs without WinTrust. ## Environment variables — portable CLI vs Windows binary -| Surface | `SIGNTOOL_RS_*` / related | Notes | +| Surface | `PSIGN_*` / related | Notes | |---------|---------------------------|--------| -| **`psign-tool-portable`** (Linux/macOS) | None required | Subcommands take **paths on the argv** only (`verify-pe`, **`trust-verify-pe`** + **`--anchor-dir` / `--authroot-cab`**, `verify-msix`, …). | -| **`psign` stub** (non-Windows) | N/A | Stub exits before Win32; env vars from parity docs do not apply. | -| **`psign` on Windows** | **`SIGNTOOL_RS_RUST_SIP`**, **`SIGNTOOL_PAGE_HASHES`** (via **`--no-page-hashes`**) | Post-sign Rust SIP digest gates and **`SignerSignEx3`** page-hash hint — see [`psign-cli-matrix.json`](psign-cli-matrix.json), [`rust-sip-architecture.md`](rust-sip-architecture.md). | -| **Parity scripts / CI** | **`SIGNTOOL_EXE`**, **`SIGNTOOL_RS_TEST_PFX`**, **`SIGNTOOL_RS_MSIX_*`**, … | Full matrix and semantics in [`ci-parity.md`](ci-parity.md). | +| **`psign-tool portable`** (Linux/macOS) | None required | Subcommands take **paths on the argv** only (`verify-pe`, **`trust-verify-pe`** + **`--anchor-dir` / `--authroot-cab`**, `verify-msix`, …). | +| **`psign-tool --mode portable`** (non-Windows) | **`PSIGN_TOOL_MODE=portable`** optional | Uses portable Rust paths where implemented; Win32-only commands fail explicitly. | +| **`psign-tool --mode windows`** (Windows) | **`PSIGN_TOOL_MODE=windows`**, **`PSIGN_RUST_SIP`**, **`SIGNTOOL_PAGE_HASHES`** (via **`--no-page-hashes`**) | Win32 backend, post-sign Rust SIP digest gates, and **`SignerSignEx3`** page-hash hint — see [`psign-cli-matrix.json`](psign-cli-matrix.json), [`rust-sip-architecture.md`](rust-sip-architecture.md). | +| **Parity scripts / CI** | **`SIGNTOOL_EXE`**, **`PSIGN_TEST_PFX`**, **`PSIGN_MSIX_*`**, … | Full matrix and semantics in [`ci-parity.md`](ci-parity.md). | ## Explicit non-goals (unless upstream specs + test vectors appear) diff --git a/docs/rust-sip-architecture.md b/docs/rust-sip-architecture.md index 3feecfc..c1024da 100644 --- a/docs/rust-sip-architecture.md +++ b/docs/rust-sip-architecture.md @@ -17,7 +17,7 @@ Hand-rolled COFF/optional-header traversal was deferred: **`object`** + the **`a | [`object`](https://crates.io/crates/object) | `PeFile32` / `PeFile64` implementing `PeTrait` | | [`sha2`](https://crates.io/crates/sha2) | SHA-256 hasher passed into `authenticode_digest` | | [`cms`](https://crates.io/crates/cms) / [`der`](https://crates.io/crates/der) | PKCS#7 **`SignedData`** decode + **`ContentInfo`** re-encode (`encode_pkcs7_content_info_signed_data_der`); **new** **`SignerInfo`** / countersignature production still TODO | -| **`pe_embed`** (in-tree) | **`WIN_CERTIFICATE`** PKCS#7 wrap + attribute-cert append + **`CheckSum`** refresh; exercised from **`psign-tool-portable`** (**`append-pe-pkcs7`**, **`pe-checksum`**) | +| **`pe_embed`** (in-tree) | **`WIN_CERTIFICATE`** PKCS#7 wrap + attribute-cert append + **`CheckSum`** refresh; exercised from **`psign-tool portable`** (**`append-pe-pkcs7`**, **`pe-checksum`**) | **Why not hand-roll PE parsing?** The main `psign` binary still uses **`goblin`** in `psign-depgraph`; digest code standardizes on **`object`** to match `authenticode-rs` and avoid duplicate COFF logic. @@ -37,7 +37,7 @@ Hand-rolled COFF/optional-header traversal was deferred: **`object`** + the **`a ## Linux / macOS CLI (`crates/psign-digest-cli`) -The **`psign-tool-portable`** binary wraps **`psign-sip-digest`** for scripting and CI (e.g. **`pe-digest`**, **`pe-checksum`**, **`verify-pe`**, **`trust-verify-*`**, **`extract-pe-pkcs7`** / **`list-pe-pkcs7`** / **`append-pe-pkcs7`**, **`inspect-pe-spc-indirect`**, **`verify-msix`**, **`pe-has-page-hashes`**, **`pe-page-hash-info`**, **`verify-pe-page-hashes`**, **`pe-authenticode-ranges`**, …). It performs **digest vs PKCS#7 indirect data** checks, **explicit-anchor trust**, **experimental PE cert-table growth**, **PE image checksum parity**, **PE Authenticode digest segment listing**, and **PE page-hash tooling** (not a full **`WinVerifyTrust`** `/ph` clone). +The **`psign-tool portable`** binary wraps **`psign-sip-digest`** for scripting and CI (e.g. **`pe-digest`**, **`pe-checksum`**, **`verify-pe`**, **`trust-verify-*`**, **`extract-pe-pkcs7`** / **`list-pe-pkcs7`** / **`append-pe-pkcs7`**, **`inspect-pe-spc-indirect`**, **`verify-msix`**, **`pe-has-page-hashes`**, **`pe-page-hash-info`**, **`verify-pe-page-hashes`**, **`pe-authenticode-ranges`**, …). It performs **digest vs PKCS#7 indirect data** checks, **explicit-anchor trust**, **experimental PE cert-table growth**, **PE image checksum parity**, **PE Authenticode digest segment listing**, and **PE page-hash tooling** (not a full **`WinVerifyTrust`** `/ph` clone). ## Win32 adapter (`src/win/sip_rust/`) diff --git a/docs/rust-sip-gaps.md b/docs/rust-sip-gaps.md index 0367f93..9784cf1 100644 --- a/docs/rust-sip-gaps.md +++ b/docs/rust-sip-gaps.md @@ -1,8 +1,8 @@ # Rust SIP — what is still missing This doc complements [`rust-sip-architecture.md`](rust-sip-architecture.md) and the parity matrix. **Rust SIP** means optional digest recomputation vs PKCS#7 indirect data after (or beside) OS **`WinVerifyTrust`** / **`SignerSignEx3`** — not replacing CryptSIP registration. Portable digest code lives in **`crates/psign-sip-digest`** (Linux CI runs its unit tests). The **`psign`** -crate **`check`**s on Linux with a stub **`main`**; full **`win`** / **`WinVerifyTrust`** paths remain Windows-only. -Use **`psign-tool-portable`** ([`crates/psign-digest-cli`](../crates/psign-digest-cli)) on Unix for portable digest / PKCS#7 consistency checks. +crate **`check`**s on Linux without the **`win`** module; full **`WinVerifyTrust`** paths remain Windows-only. +Use **`psign-tool portable`** ([`crates/psign-digest-cli`](../crates/psign-digest-cli)) on Unix for portable digest / PKCS#7 consistency checks. **CI parity:** On Ubuntu, **`ci-unix`** runs **`cargo clippy -D warnings`** on **`psign-sip-digest`**, **`psign-digest-cli`**, **`psign-authenticode-trust`**, and the **`psign` library** (CLI/`win` stays Windows-only). **`psign-digest-cli`** is additionally checked with **`--features artifact-signing-rest`**, **`azure-kv-sign-portable`**, and **`timestamp-http`**. `psign-digest-cli` integration tests run **`pe-digest`**, **`pe-signer-rs256-prehash`** / **`pkcs7-signer-rs256-prehash`** (raw **`RS256`** prehash vs library; **`extract-pe-pkcs7`** path; optional **`--signer-index`** on **`tiny32`** / **`tiny64`**), **`verify-pe`**, **`trust-verify-pe`** (failure without anchors; success with anchors — PKCS#9 **`signing-time`** on **`tiny32`** / **`tiny64`** satisfies **`--require-valid-timestamp`** with **`--prefer-timestamp-signing-time`**; other fixtures still pair **`--as-of`** with cert validity), **`pe-has-page-hashes`**, **`pe-page-hash-info`**, **`verify-pe-page-hashes`** (expects failure on tiny fixtures — no page-hash attrs), **`pe-authenticode-ranges`** on **`tiny32.signed.efi`** / **`tiny64.signed.efi`** (golden SHA-256 hex + expect **`no`** page-hash attributes / empty info for those fixtures), **`portable_verify_negative_*`** CLI failures (non-PE, **`inspect-authenticode --input pe`** on non-PE, **`trust-verify-pe`** on non-PE with configured anchors, unsigned WIM header, bad WIM header size, encrypted **`.emsix`**, non-ZIP **`.msix`**, unsigned **`.ps1`** / **`.vbs`**, unsigned CAB **`verify-cab`**, **`trust-verify-cab`** without anchors, **`cab-signer-rs256-prehash`** / **`msi-signer-rs256-prehash`** on invalid CAB / MSI bytes, invalid PKCS#7 **`inspect-authenticode`**, **`verify-catalog`** garbage, **`trust-verify-catalog`** on PE-derived **`.cat`**, **`trust-verify-detached`** when whole-file SHA-256 of the PE does not match the PKCS#7 indirect digest, **`append-pe-pkcs7`** read failures / non-PE **`--pe`**, **`pe-checksum`** / **`pe-has-page-hashes`** / **`pe-page-hash-info`** / **`pe-authenticode-ranges`** / **`verify-pe-page-hashes`** on non-PE), **`inspect_pkcs7_parity_*`**, and **`artifact-signing-metadata-check`** with JSON on stdin. Scheduled **`rust-sip-parity.yml`** job **`portable-cms-rs256-linux`** runs the RS256 digest filters above plus **`wim_verify_rejects`**, **`_unsigned_errors_`**, **`portable_verify_negative_`** (includes **`pe-digest`** / **`extract-pe-pkcs7`** / **`list-pe-pkcs7`** / **`cab-digest`** / **`inspect-pe-spc-indirect`** / **`trust-verify-detached`** / **`trust-verify-pe`** / **`inspect-authenticode`** / **`cab-signer-rs256-prehash`** / **`msi-signer-rs256-prehash`** / **`pe-signer-rs256-prehash`** / **`pkcs7-signer-rs256-prehash`** / **`catalog-signer-rs256-prehash`** error paths on bad inputs), **`inspect_pkcs7_parity_`**, **`detached_trust_`** (**`trust_verify_detached_bytes`** in **`psign-authenticode-trust`**), **`data_plane_base_url`** (**`psign-codesigning-rest`**), full **`psign-azure-kv-rest`** library unit tests (**`normalize_vault_base`**, **`kv_sign_url_from_kid`**, **`kv_jws_alg`**, …), in addition to **`rsa_pkcs1v15_signed_attrs_verify`**. **`psign-sip-digest`** library tests include **`rsa_pkcs1v15_signed_attrs_verify`** (**PKCS#1 RS256** **`verify_prehash`** vs embedded **`SignerInfo.signature`** on both tiny fixtures). @@ -36,9 +36,9 @@ Split digest (`/dg`, `/ds`, …), sealing (`/seal`, `/itos`, …), biometric/enc | Item | Status | |------|--------| -| PE **PKCS#7 encode** + **`WIN_CERTIFICATE`** embed entirely in Rust | **`pe_embed`**: **`wrap_pkcs7_der_authenticode_win_certificate`** + **`pe_append_authenticode_pkcs7_certificate`** (grow attribute cert table, patch security directory, **`pe_compute_image_checksum`**). **`pkcs7.rs`**: PKCS#9 **`messageDigest`** extract/replace (**`signer_info_pkcs9_message_digest_octets`**, **`signed_attributes_replace_pkcs9_message_digest`**), **`encapContentInfo.eContent`** digest (**`cms_digest_encapsulated_econtent_bytes`**), authenticated-attribute **`SET OF Attribute`** DER (**`signer_info_signed_attributes_sequence_der`** — **RFC 5652** §5.4), **`signer_info_sha256_digest_over_signed_attrs`** (**SHA-256** over that **`SET`** — **RSA KV `RS256`** input shape; **`rsa_pkcs1v15_signed_attrs_verify`** fixtures), and **`signer_info_clone_with_signed_attrs`** / **`signer_info_clone_with_signature_octets`** (**`SignerInfo`** patches); parse/replace **`SpcIndirectDataContent`**, **`encode_pkcs7_content_info_signed_data_der`**, **`signed_data_replace_encapsulated_spc_indirect`**, **`signed_data_replace_signer_info_at`** / **`signed_data_replace_first_signer_info`** (**`SignerInfos`** splice after remote signing). **`psign-tool-portable`**: **`pe-signer-rs256-prehash`** / **`cab-signer-rs256-prehash`** / **`msi-signer-rs256-prehash`** / **`catalog-signer-rs256-prehash`** / **`pkcs7-signer-rs256-prehash`** (**`--encoding raw`**, **`--signer-index`** for multi-**`SignerInfo`** **`SignedData`**) surfaces the **32-byte** prehash for Linux **`keys/sign`** (CAB: tail PKCS#7; MSI: **`\\u{5}DigitalSignature`** stream; **`.cat`**: whole-file PKCS#7). Full **CMS `SignerInfo` creation from scratch** (**unsigned→signed** **`SignedData`**), **ECDSA** attribute-sign rules, optional attr tweaks beyond PKCS#9 **`messageDigest`**, and production embed without templates remain OS-delegated / missing. | +| PE **PKCS#7 encode** + **`WIN_CERTIFICATE`** embed entirely in Rust | **`pe_embed`**: **`wrap_pkcs7_der_authenticode_win_certificate`** + **`pe_append_authenticode_pkcs7_certificate`** (grow attribute cert table, patch security directory, **`pe_compute_image_checksum`**). **`pkcs7.rs`**: PKCS#9 **`messageDigest`** extract/replace (**`signer_info_pkcs9_message_digest_octets`**, **`signed_attributes_replace_pkcs9_message_digest`**), **`encapContentInfo.eContent`** digest (**`cms_digest_encapsulated_econtent_bytes`**), authenticated-attribute **`SET OF Attribute`** DER (**`signer_info_signed_attributes_sequence_der`** — **RFC 5652** §5.4), **`signer_info_sha256_digest_over_signed_attrs`** (**SHA-256** over that **`SET`** — **RSA KV `RS256`** input shape; **`rsa_pkcs1v15_signed_attrs_verify`** fixtures), and **`signer_info_clone_with_signed_attrs`** / **`signer_info_clone_with_signature_octets`** (**`SignerInfo`** patches); parse/replace **`SpcIndirectDataContent`**, **`encode_pkcs7_content_info_signed_data_der`**, **`signed_data_replace_encapsulated_spc_indirect`**, **`signed_data_replace_signer_info_at`** / **`signed_data_replace_first_signer_info`** (**`SignerInfos`** splice after remote signing). **`psign-tool portable`**: **`pe-signer-rs256-prehash`** / **`cab-signer-rs256-prehash`** / **`msi-signer-rs256-prehash`** / **`catalog-signer-rs256-prehash`** / **`pkcs7-signer-rs256-prehash`** (**`--encoding raw`**, **`--signer-index`** for multi-**`SignerInfo`** **`SignedData`**) surfaces the **32-byte** prehash for Linux **`keys/sign`** (CAB: tail PKCS#7; MSI: **`\\u{5}DigitalSignature`** stream; **`.cat`**: whole-file PKCS#7). Full **CMS `SignerInfo` creation from scratch** (**unsigned→signed** **`SignedData`**), **ECDSA** attribute-sign rules, optional attr tweaks beyond PKCS#9 **`messageDigest`**, and production embed without templates remain OS-delegated / missing. | | **MSIX/Appx `CryptSIPDllCreateIndirectData`** | **`AppxSipCreateIndirectData`** / **`AppxBundleSipCreateIndirectData`** build the **APPX `SpcIndirectData`** blob at sign time; **`msix_digest`** only **verifies** recomputed AX\* vs PKCS#7 — see [`windows-signing-components.md`](windows-signing-components.md) (**AppxSip.dll**) and [`rust-sip-spec-refs.md`](rust-sip-spec-refs.md). | -| **RFC3161** timestamp construction in Rust | **Partial:** `crates/psign-sip-digest/src/timestamp.rs` — **`build_timestamp_request_bytes`** encodes **DER** **`TimeStampReq`** (version 1 + **`MessageImprint`** + optional **`nonce`** / **`certReq`**) for SHA-1 / SHA-256 / SHA-384 / SHA-512; **`parse_time_stamp_resp_der`** reads **`PKIStatusInfo.status`** (**`INTEGER`**, including padded forms like **`00 80`** → **128**), optional **`statusString`** (**`PKIFreeText`** as a sequence of **`UTF8String`**, including empty **`SEQUENCE`**), optional **`failInfo`** (**`BIT STRING`** TLV, after **`statusString`** if both appear), plus optional raw **`timeStampToken`** (no CMS verify); **`pkifailure_info_flag_labels_from_bit_string_tlv`** decodes **`failInfo`** bits to RFC 2510 names (**`badAlg`**–**`badPOP`**) / **`bit_N`**. **`psign-tool-portable`**: **`rfc3161-timestamp-req`**, **`rfc3161-timestamp-resp-inspect`** (prints **`pki_status_int`**, **`time_stamp_token_len`**, first **16** octets of the token TLV as **`time_stamp_token_prefix_hex`** for CMS sniffing, **`status_strings_json`**, **`fail_info_tlv_hex`**, **`fail_info_flags_json`**), optional **`rfc3161-timestamp-http-post`** with **`--features timestamp-http`**. **Token / `MessageImprint` crypto** verification and **Authenticode countersignature embed** remain out of scope. Portable trust **`--prefer-timestamp-signing-time`** / **`--require-valid-timestamp`** reads nested **`TSTInfo.genTime`** / PKCS#9 **`signing-time`** for **`exact_date`** (`crates/psign-authenticode-trust/src/rfc3161_extract.rs`); **full TSA chain + signed digest check** is not implemented — see **`linux_trust_rfc3161_tsa_crypto_gap`**. | +| **RFC3161** timestamp construction in Rust | **Partial:** `crates/psign-sip-digest/src/timestamp.rs` — **`build_timestamp_request_bytes`** encodes **DER** **`TimeStampReq`** (version 1 + **`MessageImprint`** + optional **`nonce`** / **`certReq`**) for SHA-1 / SHA-256 / SHA-384 / SHA-512; **`parse_time_stamp_resp_der`** reads **`PKIStatusInfo.status`** (**`INTEGER`**, including padded forms like **`00 80`** → **128**), optional **`statusString`** (**`PKIFreeText`** as a sequence of **`UTF8String`**, including empty **`SEQUENCE`**), optional **`failInfo`** (**`BIT STRING`** TLV, after **`statusString`** if both appear), plus optional raw **`timeStampToken`** (no CMS verify); **`pkifailure_info_flag_labels_from_bit_string_tlv`** decodes **`failInfo`** bits to RFC 2510 names (**`badAlg`**–**`badPOP`**) / **`bit_N`**. **`psign-tool portable`**: **`rfc3161-timestamp-req`**, **`rfc3161-timestamp-resp-inspect`** (prints **`pki_status_int`**, **`time_stamp_token_len`**, first **16** octets of the token TLV as **`time_stamp_token_prefix_hex`** for CMS sniffing, **`status_strings_json`**, **`fail_info_tlv_hex`**, **`fail_info_flags_json`**), optional **`rfc3161-timestamp-http-post`** with **`--features timestamp-http`**. **Token / `MessageImprint` crypto** verification and **Authenticode countersignature embed** remain out of scope. Portable trust **`--prefer-timestamp-signing-time`** / **`--require-valid-timestamp`** reads nested **`TSTInfo.genTime`** / PKCS#9 **`signing-time`** for **`exact_date`** (`crates/psign-authenticode-trust/src/rfc3161_extract.rs`); **full TSA chain + signed digest check** is not implemented — see **`linux_trust_rfc3161_tsa_crypto_gap`**. | | **`/ph`** **page hashes** (`SPC_PE_IMAGE_PAGE_HASHES`) | Portable **CMS extract** + **payload peel** + **flat `(offset,digest)*` parse** + **experimental contiguous file-offset verify** (`page_hashes`, CLI **`pe-has-page-hashes`** / **`pe-page-hash-info`** / **`verify-pe-page-hashes`**). Differs from **`WinVerifyTrust`** where checksum / security-directory handling diverges — native **`verify --verify-page-hashes`** remains the strict `/ph` reference. | | **MSIX/Appx `SignerSignEx3` signing** (`psign sign` on `.msix`) | **`APPX_SIP_CLIENT_DATA`** + **`SIGNER_SIGN_EX2_PARAMS`** as **`pSipData`** for all cleartext **`MsixFamily`** paths (embedded and **`/dlib`** decoupled) so **`AppxSip.dll`** receives **`SIP_SUBJECTINFO.pClientData`**. CI may still record **`documented_rust_msix_sign_ex3_gap`** when native succeeds but Rust fails (**`CRYPT_E_NO_PROVIDER`** `0x80092006`, publisher / manifest mismatches, etc.). **`CreateFileW`** subject handle + **`--debug`** diagnostics remain; **`pCryptoPolicy`** is still **`NULL`** — see [**SignerSignEx3 / SIP glue**](rust-sip-spec-refs.md#signersignex3-and-sip-glue). **Publisher-vs-signer binding** is enforced in **`AppxSip.dll`** (manifest vs PKCS#7 signer), not in **`msix_digest`** — see [`windows-signing-components.md`](windows-signing-components.md). | | **PowerShell-class script SIP line discipline** | **`pwrshsip.dll`** decompilation shows extension dispatch for `.ps1`, `.ps1xml`, `.psc1`, `.psd1`, `.psm1`, `.cdxml`, and `.mof`; BOM / `RtlIsTextUnicode` format probing; UTF-16 line-oriented hashing; marker families `# SIG #`, XML comments, and `/* SIG # */`; base64 extraction through `CryptStringToBinaryW`. The Rust `ps_script` module mirrors markers and BOM-aware UTF-16 stripping, but remains a heuristic consistency checker rather than a byte-for-byte clone of every line-reader and malformed-block error. | @@ -64,7 +64,7 @@ Prioritize based on whether you need **offline signing** without `mssign32` or * ## Next milestones (suggested order) -1. **PE page-hash segments** — Align **contiguous verify** with **`WinVerifyTrust`** exclusions (checksum field, security dir pointer, certificate table) and add a fixture signed **with** `/ph` for regression tests (Linux CI via `psign-tool-portable` / `psign-sip-digest`). +1. **PE page-hash segments** — Align **contiguous verify** with **`WinVerifyTrust`** exclusions (checksum field, security dir pointer, certificate table) and add a fixture signed **with** `/ph` for regression tests (Linux CI via `psign-tool portable` / `psign-sip-digest`). 2. **Rust PKCS#7 encode + `WIN_CERTIFICATE` embed** — Outer **`ContentInfo`** re-encode from **`SignedData`** exists (**`encode_pkcs7_content_info_signed_data_der`**); **RSA** **`RS256`** authenticated-attribute digest ↔ **`SignerInfo.signature`** parity is tested (**`rsa_pkcs1v15_signed_attrs_verify`**); **`pe-signer-rs256-prehash`** + KV **`keys/sign`** + **`signer_info_clone_with_signature_octets`** + **`signed_data_replace_first_signer_info`** compose the remote half when mutating an existing PKCS#7. Still need **`SignerInfo`** / **`SignedData`** assembly **from scratch**, **ECDSA**, timestamps, and full **`unsigned→signed`** parity; intersects split-digest (`/dg`, `/ds`) backlog. 3. **Encrypted MSIX (`EappxSip*`)** — Requires Windows encrypted-package crypto or constrained FFI; not a ZIP-only digest. 4. **VBA / `mso.dll`** — Only viable near-term via **`VBE7`** FFI or accepting permanent OS delegation. diff --git a/docs/rust-sip-spec-refs.md b/docs/rust-sip-spec-refs.md index 25e9997..a1b92a3 100644 --- a/docs/rust-sip-spec-refs.md +++ b/docs/rust-sip-spec-refs.md @@ -23,7 +23,7 @@ Authoritative references for in-process Authenticode “SIP logic” (digest sco The **subject digest** embedded in `SpcIndirectDataContent` follows **`authenticode_digest`**: it hashes the PE in **several disjoint file ranges** — skipping the optional-header **checksum** DWORD, skipping the **security directory** slot in the data directory, hashing **sections in ascending virtual/raw start order**, then any trailing file tail **excluding the WIN_CERTIFICATE table**. See [`authenticode-rs` `authenticode_digest.rs`](https://github.com/google/authenticode-rs/blob/main/authenticode/src/authenticode_digest.rs). -**Page-hash** authenticated attributes (`1.3.6.1.4.1.311.2.3.1` / `.2`) carry a separate flat table of **`(end_offset, digest)`** pairs. Native **`WinVerifyTrust`** `/ph` validates those entries against PE bytes with rules that **do not** reduce to “hash contiguous raw slices from offset 0”. The portable CLI command **`psign-tool-portable verify-pe-page-hashes`** implements an **experimental contiguous raw-file model** only — closing Tier 1c parity requires matching **`WINTRUST`/`CryptSIP`** page-boundary and exclusion semantics (or reusing their outputs via FFI on Windows). +**Page-hash** authenticated attributes (`1.3.6.1.4.1.311.2.3.1` / `.2`) carry a separate flat table of **`(end_offset, digest)`** pairs. Native **`WinVerifyTrust`** `/ph` validates those entries against PE bytes with rules that **do not** reduce to “hash contiguous raw slices from offset 0”. The portable CLI command **`psign-tool portable verify-pe-page-hashes`** implements an **experimental contiguous raw-file model** only — closing Tier 1c parity requires matching **`WINTRUST`/`CryptSIP`** page-boundary and exclusion semantics (or reusing their outputs via FFI on Windows). The **subject-digest** disjoint ranges are enumerated in-code as **`pe_authenticode_digest_file_ranges`** for tooling (same segment order as **`authenticode_digest`**). diff --git a/scripts/ci/bootstrap-devolutions-authenticode.ps1 b/scripts/ci/bootstrap-devolutions-authenticode.ps1 index 74edd3d..9dc593a 100644 --- a/scripts/ci/bootstrap-devolutions-authenticode.ps1 +++ b/scripts/ci/bootstrap-devolutions-authenticode.ps1 @@ -62,12 +62,12 @@ catch { $pfxPassword = "CodeSign123!" $lines = @( - "SIGNTOOL_RS_TEST_PFX=$pfxPath", - "SIGNTOOL_RS_TEST_PFX_PASSWORD=$pfxPassword", - "SIGNTOOL_RS_TIMESTAMP_URL=$TimestampUrl", - "SIGNTOOL_RS_MSIX_TEST_PFX=$pfxPath", - "SIGNTOOL_RS_MSIX_TEST_PFX_PASSWORD=$pfxPassword", - "SIGNTOOL_RS_MSIX_TIMESTAMP_URL=$TimestampUrl" + "PSIGN_TEST_PFX=$pfxPath", + "PSIGN_TEST_PFX_PASSWORD=$pfxPassword", + "PSIGN_TIMESTAMP_URL=$TimestampUrl", + "PSIGN_MSIX_TEST_PFX=$pfxPath", + "PSIGN_MSIX_TEST_PFX_PASSWORD=$pfxPassword", + "PSIGN_MSIX_TIMESTAMP_URL=$TimestampUrl" ) # Rust signer signs from store via --cert-sha1 reliably for this PKI; native signtool keeps using /f PFX. @@ -76,13 +76,13 @@ try { $imported = Import-PfxCertificate -FilePath $pfxPath -CertStoreLocation "Cert:\CurrentUser\My" -Password $secure -Exportable $thumb = $imported.Thumbprint $lines += @( - "SIGNTOOL_RS_TEST_CERT_SHA1=$thumb", - "SIGNTOOL_RS_MSIX_TEST_CERT_SHA1=$thumb" + "PSIGN_TEST_CERT_SHA1=$thumb", + "PSIGN_MSIX_TEST_CERT_SHA1=$thumb" ) Write-Host "Imported test signing cert to CurrentUser\My (thumbprint $thumb)." } catch { - Write-Warning "Import-PfxCertificate to CurrentUser\My failed ($($_.Exception.Message)); set SIGNTOOL_RS_TEST_CERT_SHA1 manually if Rust --pfx signing fails." + Write-Warning "Import-PfxCertificate to CurrentUser\My failed ($($_.Exception.Message)); set PSIGN_TEST_CERT_SHA1 manually if Rust --pfx signing fails." } foreach ($line in $lines) { diff --git a/scripts/ci/build-catalog-pe-pkcs7-fixture.ps1 b/scripts/ci/build-catalog-pe-pkcs7-fixture.ps1 index 56076eb..01d2b76 100644 --- a/scripts/ci/build-catalog-pe-pkcs7-fixture.ps1 +++ b/scripts/ci/build-catalog-pe-pkcs7-fixture.ps1 @@ -13,8 +13,8 @@ if (-not (Test-Path -LiteralPath $pe)) { throw "Missing PE fixture: $pe" } New-Item -ItemType Directory -Force -Path (Split-Path $out) | Out-Null Push-Location $WorkspaceRoot try { - cargo run -p psign-digest-cli --bin psign-tool-portable --locked -- ` - extract-pe-pkcs7 $pe --output $out + cargo run -p psign --bin psign-tool --locked -- ` + portable extract-pe-pkcs7 $pe --output $out if ($LASTEXITCODE -ne 0) { throw "extract-pe-pkcs7 failed" } } finally { diff --git a/scripts/ci/build-signed-cab-fixture.ps1 b/scripts/ci/build-signed-cab-fixture.ps1 index 0884d08..f9c91c5 100644 --- a/scripts/ci/build-signed-cab-fixture.ps1 +++ b/scripts/ci/build-signed-cab-fixture.ps1 @@ -14,8 +14,8 @@ if (-not $WorkspaceRoot) { $WorkspaceRoot = (Resolve-Path (Join-Path $PSScriptRoot "..\..")).Path } -if (-not $env:SIGNTOOL_RS_TEST_PFX -or -not (Test-Path -LiteralPath $env:SIGNTOOL_RS_TEST_PFX)) { - throw "Set SIGNTOOL_RS_TEST_PFX (run scripts/ci/bootstrap-devolutions-authenticode.ps1 or set env manually)." +if (-not $env:PSIGN_TEST_PFX -or -not (Test-Path -LiteralPath $env:PSIGN_TEST_PFX)) { + throw "Set PSIGN_TEST_PFX (run scripts/ci/bootstrap-devolutions-authenticode.ps1 or set env manually)." } $kitBinRoot = Join-Path ${env:ProgramFiles(x86)} "Windows Kits\10\bin" @@ -68,9 +68,9 @@ $payload $outDir = Split-Path $outAbs -Parent New-Item -ItemType Directory -Force -Path $outDir | Out-Null - $signArgs = @("sign", "/fd", "SHA256", "/f", $env:SIGNTOOL_RS_TEST_PFX, $cabPath) - if ($env:SIGNTOOL_RS_TEST_PFX_PASSWORD) { - $signArgs = @("sign", "/fd", "SHA256", "/f", $env:SIGNTOOL_RS_TEST_PFX, "/p", $env:SIGNTOOL_RS_TEST_PFX_PASSWORD, $cabPath) + $signArgs = @("sign", "/fd", "SHA256", "/f", $env:PSIGN_TEST_PFX, $cabPath) + if ($env:PSIGN_TEST_PFX_PASSWORD) { + $signArgs = @("sign", "/fd", "SHA256", "/f", $env:PSIGN_TEST_PFX, "/p", $env:PSIGN_TEST_PFX_PASSWORD, $cabPath) } & $signtool @signArgs if ($LASTEXITCODE -ne 0) { throw "signtool sign failed" } diff --git a/scripts/ci/pack-minimal-winmd.ps1 b/scripts/ci/pack-minimal-winmd.ps1 index 27b1f12..a946516 100644 --- a/scripts/ci/pack-minimal-winmd.ps1 +++ b/scripts/ci/pack-minimal-winmd.ps1 @@ -1,7 +1,7 @@ # Produce an unsigned `.winmd` path for Authenticode parity by copying PE bytes. # # Windows metadata (`.winmd`) is PE-backed; `SignerSignEx3` / inbox SIP treat it like a PE image subject. -# CI uses the same unsigned build output as `SIGNTOOL_RS_UNSIGNED_FIXTURE`, renamed for extension-driven SIP selection. +# CI uses the same unsigned build output as `PSIGN_UNSIGNED_FIXTURE`, renamed for extension-driven SIP selection. param( [Parameter(Mandatory)][string]$PeSource, [Parameter(Mandatory)][string]$OutputWinmd diff --git a/scripts/ci/prepare-parity-fixtures.ps1 b/scripts/ci/prepare-parity-fixtures.ps1 index 724aff6..d9de71d 100644 --- a/scripts/ci/prepare-parity-fixtures.ps1 +++ b/scripts/ci/prepare-parity-fixtures.ps1 @@ -52,12 +52,12 @@ if ($p7Exit -ne 0 -or -not $p7File) { if ($RequireDetachedPkcs7) { throw "Detached PKCS#7 generation failed (exit $p7Exit). Native signtool /p7 output: $p7Dir" } - Write-Warning "Detached PKCS#7 generation failed (exit $p7Exit); omit SIGNTOOL_RS_DETACHED_* from CI env." + Write-Warning "Detached PKCS#7 generation failed (exit $p7Exit); omit PSIGN_DETACHED_* from CI env." } else { $detachedLines = @( - "SIGNTOOL_RS_DETACHED_CONTENT=$contentPath", - "SIGNTOOL_RS_DETACHED_PKCS7=$($p7File.FullName)" + "PSIGN_DETACHED_CONTENT=$contentPath", + "PSIGN_DETACHED_PKCS7=$($p7File.FullName)" ) foreach ($line in $detachedLines) { $name, $value = $line.Split("=", 2) @@ -68,13 +68,13 @@ else { } } -$signedLines = @("SIGNTOOL_RS_SIGNED_FIXTURE=$tempCopy") -Set-Item -Path "Env:SIGNTOOL_RS_SIGNED_FIXTURE" -Value $tempCopy +$signedLines = @("PSIGN_SIGNED_FIXTURE=$tempCopy") +Set-Item -Path "Env:PSIGN_SIGNED_FIXTURE" -Value $tempCopy if ($EmitGithubEnv -and $env:GITHUB_ENV) { Add-Content -LiteralPath $env:GITHUB_ENV -Value $signedLines } -Write-Host "Prepared SIGNTOOL_RS_SIGNED_FIXTURE=$tempCopy" +Write-Host "Prepared PSIGN_SIGNED_FIXTURE=$tempCopy" if ($p7File) { Write-Host "Prepared detached PKCS#7 $($p7File.FullName)" } diff --git a/scripts/ci/run-exhaustive-parity-ci.ps1 b/scripts/ci/run-exhaustive-parity-ci.ps1 index c97b254..30bfb1b 100644 --- a/scripts/ci/run-exhaustive-parity-ci.ps1 +++ b/scripts/ci/run-exhaustive-parity-ci.ps1 @@ -1,7 +1,7 @@ # Orchestrate Devolutions PKI bootstrap, derived fixtures, minimal MSIX pack, and run-parity-diff with exhaustive semantic gates. param( [string]$WorkspaceRoot, - [string]$UnsignedPeRel = "target\debug\psign-tool-windows.exe", + [string]$UnsignedPeRel = "target\debug\psign-tool.exe", [switch]$SkipMsixParitySignReport ) @@ -15,14 +15,14 @@ Set-Location -LiteralPath $WorkspaceRoot $unsignedPe = Join-Path $WorkspaceRoot $UnsignedPeRel if (-not (Test-Path -LiteralPath $unsignedPe)) { - throw "Unsigned PE not found (build psign-tool-windows first): $unsignedPe" + throw "Unsigned PE not found (build psign-tool first): $unsignedPe" } & (Join-Path $PSScriptRoot "prepare-parity-fixtures.ps1") ` -WorkspaceRoot $WorkspaceRoot ` -UnsignedPe $unsignedPe ` - -PfxPath $env:SIGNTOOL_RS_TEST_PFX ` - -PfxPassword $env:SIGNTOOL_RS_TEST_PFX_PASSWORD ` + -PfxPath $env:PSIGN_TEST_PFX ` + -PfxPassword $env:PSIGN_TEST_PFX_PASSWORD ` -EmitGithubEnv ` -RequireDetachedPkcs7 @@ -33,22 +33,22 @@ $msixOut = Join-Path $rt "psign_parity_minimal.msix" -PeAsExecutable $unsignedPe ` -OutputMsix $msixOut -$env:SIGNTOOL_RS_MSIX_UNSIGNED_FIXTURE = $msixOut -$env:SIGNTOOL_RS_UNSIGNED_FIXTURE = $unsignedPe +$env:PSIGN_MSIX_UNSIGNED_FIXTURE = $msixOut +$env:PSIGN_UNSIGNED_FIXTURE = $unsignedPe $winmdOut = Join-Path $rt "psign_parity_minimal.winmd" & (Join-Path $PSScriptRoot "pack-minimal-winmd.ps1") -PeSource $unsignedPe -OutputWinmd $winmdOut -$env:SIGNTOOL_RS_WINMD_UNSIGNED_FIXTURE = $winmdOut -if ($env:SIGNTOOL_RS_TIMESTAMP_URL) { - $env:SIGNTOOL_RS_WINMD_TIMESTAMP_URL = $env:SIGNTOOL_RS_TIMESTAMP_URL +$env:PSIGN_WINMD_UNSIGNED_FIXTURE = $winmdOut +if ($env:PSIGN_TIMESTAMP_URL) { + $env:PSIGN_WINMD_TIMESTAMP_URL = $env:PSIGN_TIMESTAMP_URL } if ($env:GITHUB_ENV) { - Add-Content -LiteralPath $env:GITHUB_ENV -Value "SIGNTOOL_RS_MSIX_UNSIGNED_FIXTURE=$msixOut" - Add-Content -LiteralPath $env:GITHUB_ENV -Value "SIGNTOOL_RS_UNSIGNED_FIXTURE=$unsignedPe" - Add-Content -LiteralPath $env:GITHUB_ENV -Value "SIGNTOOL_RS_WINMD_UNSIGNED_FIXTURE=$winmdOut" - if ($env:SIGNTOOL_RS_WINMD_TIMESTAMP_URL) { - Add-Content -LiteralPath $env:GITHUB_ENV -Value "SIGNTOOL_RS_WINMD_TIMESTAMP_URL=$($env:SIGNTOOL_RS_WINMD_TIMESTAMP_URL)" + Add-Content -LiteralPath $env:GITHUB_ENV -Value "PSIGN_MSIX_UNSIGNED_FIXTURE=$msixOut" + Add-Content -LiteralPath $env:GITHUB_ENV -Value "PSIGN_UNSIGNED_FIXTURE=$unsignedPe" + Add-Content -LiteralPath $env:GITHUB_ENV -Value "PSIGN_WINMD_UNSIGNED_FIXTURE=$winmdOut" + if ($env:PSIGN_WINMD_TIMESTAMP_URL) { + Add-Content -LiteralPath $env:GITHUB_ENV -Value "PSIGN_WINMD_TIMESTAMP_URL=$($env:PSIGN_WINMD_TIMESTAMP_URL)" } } diff --git a/scripts/linux-portable-validation.sh b/scripts/linux-portable-validation.sh index ad4092e..7068a3a 100644 --- a/scripts/linux-portable-validation.sh +++ b/scripts/linux-portable-validation.sh @@ -41,16 +41,16 @@ cargo test -p psign-codesigning-rest --lib --locked echo "== unit tests: azure-kv-rest ==" cargo test -p psign-azure-kv-rest --lib --locked -echo "== integration: psign-tool-portable (digest-cli) ==" +echo "== integration: psign-tool portable (digest-cli) ==" cargo test -p psign-digest-cli --locked -echo "== integration: psign-tool-portable (artifact-signing-rest) ==" +echo "== integration: psign-tool portable (artifact-signing-rest) ==" cargo test -p psign-digest-cli --features artifact-signing-rest --locked -echo "== integration: psign-tool-portable (azure-kv-sign-portable) ==" +echo "== integration: psign-tool portable (azure-kv-sign-portable) ==" cargo test -p psign-digest-cli --features azure-kv-sign-portable --locked -echo "== integration: psign-tool-portable (timestamp-http) ==" +echo "== integration: psign-tool portable (timestamp-http) ==" cargo test -p psign-digest-cli --features timestamp-http --locked echo "== psign library tests (argv / response files) ==" diff --git a/scripts/msix-parity-sign.ps1 b/scripts/msix-parity-sign.ps1 index b4af95c..f1187d9 100644 --- a/scripts/msix-parity-sign.ps1 +++ b/scripts/msix-parity-sign.ps1 @@ -1,11 +1,11 @@ param( - [string]$UnsignedMsix = $env:SIGNTOOL_RS_MSIX_UNSIGNED_FIXTURE, - [string]$PfxPath = $env:SIGNTOOL_RS_MSIX_TEST_PFX, - [string]$PfxPassword = $env:SIGNTOOL_RS_MSIX_TEST_PFX_PASSWORD, - [string]$CertSha1 = $env:SIGNTOOL_RS_MSIX_TEST_CERT_SHA1, - [string]$TimestampUrl = $env:SIGNTOOL_RS_MSIX_TIMESTAMP_URL, - [string]$DlibPath = $env:SIGNTOOL_RS_MSIX_DLIB, - [string]$DmdfPath = $env:SIGNTOOL_RS_MSIX_DMDF, + [string]$UnsignedMsix = $env:PSIGN_MSIX_UNSIGNED_FIXTURE, + [string]$PfxPath = $env:PSIGN_MSIX_TEST_PFX, + [string]$PfxPassword = $env:PSIGN_MSIX_TEST_PFX_PASSWORD, + [string]$CertSha1 = $env:PSIGN_MSIX_TEST_CERT_SHA1, + [string]$TimestampUrl = $env:PSIGN_MSIX_TIMESTAMP_URL, + [string]$DlibPath = $env:PSIGN_MSIX_DLIB, + [string]$DmdfPath = $env:PSIGN_MSIX_DMDF, [string]$Digest = "SHA256", [string]$TimestampDigest = "SHA256", [string]$ReportPath, @@ -51,12 +51,12 @@ function Resolve-SignTool { } function Resolve-RustBin { - $rustBin = Join-Path $workspace "target\debug\psign-tool-windows.exe" + $rustBin = Join-Path $workspace "target\debug\psign-tool.exe" if (-not (Test-Path -LiteralPath $rustBin)) { - cargo build -p psign --bin psign-tool-windows | Out-Null + cargo build -p psign --bin psign-tool | Out-Null } if (-not (Test-Path -LiteralPath $rustBin)) { - throw "Unable to locate psign-tool-windows.exe after build." + throw "Unable to locate psign-tool.exe after build." } return $rustBin } @@ -91,7 +91,7 @@ if (-not (Test-Path -LiteralPath $UnsignedMsix)) { throw "Unsigned MSIX not found: $UnsignedMsix" } if (-not $PfxPath -and -not $CertSha1) { - throw "Provide either -PfxPath (or SIGNTOOL_RS_MSIX_TEST_PFX) or -CertSha1 (or SIGNTOOL_RS_MSIX_TEST_CERT_SHA1)." + throw "Provide either -PfxPath (or PSIGN_MSIX_TEST_PFX) or -CertSha1 (or PSIGN_MSIX_TEST_CERT_SHA1)." } if ($PfxPath -and -not (Test-Path -LiteralPath $PfxPath)) { throw "PFX not found: $PfxPath" @@ -133,11 +133,11 @@ if ($decoupled) { $nativeSignArgs += @($nativeMsix) $rustThumbFromEnv = $null -if ($env:SIGNTOOL_RS_MSIX_TEST_CERT_SHA1 -and $env:SIGNTOOL_RS_MSIX_TEST_CERT_SHA1.Trim()) { - $rustThumbFromEnv = $env:SIGNTOOL_RS_MSIX_TEST_CERT_SHA1.Trim() +if ($env:PSIGN_MSIX_TEST_CERT_SHA1 -and $env:PSIGN_MSIX_TEST_CERT_SHA1.Trim()) { + $rustThumbFromEnv = $env:PSIGN_MSIX_TEST_CERT_SHA1.Trim() } -elseif ($env:SIGNTOOL_RS_TEST_CERT_SHA1 -and $env:SIGNTOOL_RS_TEST_CERT_SHA1.Trim()) { - $rustThumbFromEnv = $env:SIGNTOOL_RS_TEST_CERT_SHA1.Trim() +elseif ($env:PSIGN_TEST_CERT_SHA1 -and $env:PSIGN_TEST_CERT_SHA1.Trim()) { + $rustThumbFromEnv = $env:PSIGN_TEST_CERT_SHA1.Trim() } $rustSignArgs = @("sign", "--digest", $Digest.ToLowerInvariant(), "--timestamp-url", $TimestampUrl, "--timestamp-digest", $TimestampDigest.ToLowerInvariant()) diff --git a/scripts/run-parity-diff.ps1 b/scripts/run-parity-diff.ps1 index 03871dc..78ff59a 100644 --- a/scripts/run-parity-diff.ps1 +++ b/scripts/run-parity-diff.ps1 @@ -42,10 +42,10 @@ function Resolve-SignTool { $nativeSignTool = Resolve-SignTool $workspace = Split-Path -Parent $PSScriptRoot -$rustBin = Join-Path $workspace "target\debug\psign-tool-windows.exe" +$rustBin = Join-Path $workspace "target\debug\psign-tool.exe" if (-not (Test-Path -LiteralPath $rustBin)) { - cargo build -p psign --bin psign-tool-windows | Out-Null + cargo build -p psign --bin psign-tool | Out-Null } if ($MsixOnly) { @@ -404,10 +404,10 @@ $results += [PSCustomObject]@{ } # Optional: native /tseal vs rust --tseal exit codes on the same signed artifact. -if ($env:SIGNTOOL_RS_SIGNED_FIXTURE -and $env:SIGNTOOL_RS_TIMESTAMP_URL) { +if ($env:PSIGN_SIGNED_FIXTURE -and $env:PSIGN_TIMESTAMP_URL) { $expectedScenarioIds += @("timestamp_tseal_exit_parity", "timestamp_tr_two_targets_exit_match") - $tsUrl = $env:SIGNTOOL_RS_TIMESTAMP_URL - $signedPath = $env:SIGNTOOL_RS_SIGNED_FIXTURE + $tsUrl = $env:PSIGN_TIMESTAMP_URL + $signedPath = $env:PSIGN_SIGNED_FIXTURE $saved = $ErrorActionPreference $ErrorActionPreference = "Continue" & "$nativeSignTool" timestamp /tseal $tsUrl /td SHA256 $signedPath 2>&1 | Out-Null @@ -448,13 +448,13 @@ function Test-EnvPresent([string]$Name) { } function Get-RustSignCredentialArgs { - $thumb = $env:SIGNTOOL_RS_TEST_CERT_SHA1 + $thumb = $env:PSIGN_TEST_CERT_SHA1 if ($thumb -and $thumb.Trim().Length -gt 0) { return @("--cert-sha1", $thumb.Trim()) } - $out = @("--pfx", $env:SIGNTOOL_RS_TEST_PFX) - if ($env:SIGNTOOL_RS_TEST_PFX_PASSWORD) { - $out += @("--password", $env:SIGNTOOL_RS_TEST_PFX_PASSWORD) + $out = @("--pfx", $env:PSIGN_TEST_PFX) + if ($env:PSIGN_TEST_PFX_PASSWORD) { + $out += @("--password", $env:PSIGN_TEST_PFX_PASSWORD) } return $out } @@ -462,28 +462,28 @@ function Get-RustSignCredentialArgs { function Get-RustMsixCredentialArgs { # Prefer store thumbprint when CI bootstrap imported the test cert into `CurrentUser\My` — Rust `SignerSignEx3` # + MSIX SIP often succeeds with `--cert-sha1` while `--pfx` can hit `CRYPT_E_NO_PROVIDER` on some hosts. - $thumb = $env:SIGNTOOL_RS_MSIX_TEST_CERT_SHA1 + $thumb = $env:PSIGN_MSIX_TEST_CERT_SHA1 if (-not $thumb -or $thumb.Trim().Length -eq 0) { - $thumb = $env:SIGNTOOL_RS_TEST_CERT_SHA1 + $thumb = $env:PSIGN_TEST_CERT_SHA1 } if ($thumb -and $thumb.Trim().Length -gt 0) { return @("--cert-sha1", $thumb.Trim()) } - $pfx = $env:SIGNTOOL_RS_MSIX_TEST_PFX + $pfx = $env:PSIGN_MSIX_TEST_PFX if ($pfx -and $pfx.Trim().Length -gt 0 -and (Test-Path -LiteralPath $pfx.Trim())) { $out = @("--pfx", $pfx.Trim()) - if ($env:SIGNTOOL_RS_MSIX_TEST_PFX_PASSWORD) { - $out += @("--password", $env:SIGNTOOL_RS_MSIX_TEST_PFX_PASSWORD) + if ($env:PSIGN_MSIX_TEST_PFX_PASSWORD) { + $out += @("--password", $env:PSIGN_MSIX_TEST_PFX_PASSWORD) } return $out } - throw "Get-RustMsixCredentialArgs: set SIGNTOOL_RS_TEST_CERT_SHA1 (after bootstrap) or SIGNTOOL_RS_MSIX_TEST_PFX (see scripts/ci/bootstrap-devolutions-authenticode.ps1)." + throw "Get-RustMsixCredentialArgs: set PSIGN_TEST_CERT_SHA1 (after bootstrap) or PSIGN_MSIX_TEST_PFX (see scripts/ci/bootstrap-devolutions-authenticode.ps1)." } if ($FailOnSemantic) { $requiredCore = @( - "SIGNTOOL_RS_UNSIGNED_FIXTURE", - "SIGNTOOL_RS_TEST_PFX" + "PSIGN_UNSIGNED_FIXTURE", + "PSIGN_TEST_PFX" ) $missingCore = @($requiredCore | Where-Object { -not (Test-EnvPresent $_) }) if ($missingCore.Count -gt 0) { @@ -493,13 +493,13 @@ if ($FailOnSemantic) { if ($FailOnSemantic -and $FailOnSemanticExhaustive) { $requiredExhaustive = @( - "SIGNTOOL_RS_SIGNED_FIXTURE", - "SIGNTOOL_RS_TIMESTAMP_URL", - "SIGNTOOL_RS_MSIX_UNSIGNED_FIXTURE", - "SIGNTOOL_RS_MSIX_TEST_PFX", - "SIGNTOOL_RS_MSIX_TIMESTAMP_URL", - "SIGNTOOL_RS_DETACHED_CONTENT", - "SIGNTOOL_RS_DETACHED_PKCS7" + "PSIGN_SIGNED_FIXTURE", + "PSIGN_TIMESTAMP_URL", + "PSIGN_MSIX_UNSIGNED_FIXTURE", + "PSIGN_MSIX_TEST_PFX", + "PSIGN_MSIX_TIMESTAMP_URL", + "PSIGN_DETACHED_CONTENT", + "PSIGN_DETACHED_PKCS7" ) $missingEx = @($requiredExhaustive | Where-Object { -not (Test-EnvPresent $_) }) if ($missingEx.Count -gt 0) { @@ -508,7 +508,7 @@ if ($FailOnSemantic -and $FailOnSemanticExhaustive) { } # Artifact-semantic scenario: sign + verify output includes expected signature markers. -if ($env:SIGNTOOL_RS_UNSIGNED_FIXTURE -and $env:SIGNTOOL_RS_TEST_PFX) { +if ($env:PSIGN_UNSIGNED_FIXTURE -and $env:PSIGN_TEST_PFX) { $expectedScenarioIds += @( "artifact_sign_verify_semantic", "artifact_sign_two_pe_exit_parity", @@ -517,7 +517,7 @@ if ($env:SIGNTOOL_RS_UNSIGNED_FIXTURE -and $env:SIGNTOOL_RS_TEST_PFX) { "verify_pe_fixture_pa_exit_match" ) $tempSigned = Join-Path $env:TEMP "psign_ci_semantic.exe" - Copy-Item -LiteralPath $env:SIGNTOOL_RS_UNSIGNED_FIXTURE -Destination $tempSigned -Force + Copy-Item -LiteralPath $env:PSIGN_UNSIGNED_FIXTURE -Destination $tempSigned -Force $signArgs = @("sign") + (Get-RustSignCredentialArgs) + @("--digest", "sha256", $tempSigned) $saved = $ErrorActionPreference @@ -542,13 +542,13 @@ if ($env:SIGNTOOL_RS_UNSIGNED_FIXTURE -and $env:SIGNTOOL_RS_TEST_PFX) { # Native `` vs rust trailing `files`: same PFX/options, two unsigned PE copies — exit codes only. $tmpSignA = Join-Path $env:TEMP "psign_sign_two_a.exe" $tmpSignB = Join-Path $env:TEMP "psign_sign_two_b.exe" - Copy-Item -LiteralPath $env:SIGNTOOL_RS_UNSIGNED_FIXTURE -Destination $tmpSignA -Force - Copy-Item -LiteralPath $env:SIGNTOOL_RS_UNSIGNED_FIXTURE -Destination $tmpSignB -Force + Copy-Item -LiteralPath $env:PSIGN_UNSIGNED_FIXTURE -Destination $tmpSignA -Force + Copy-Item -LiteralPath $env:PSIGN_UNSIGNED_FIXTURE -Destination $tmpSignB -Force $savedTwo = $ErrorActionPreference $ErrorActionPreference = "Continue" - $nativeTwoSign = @("sign", "/fd", "SHA256", "/f", $env:SIGNTOOL_RS_TEST_PFX, $tmpSignA, $tmpSignB) - if ($env:SIGNTOOL_RS_TEST_PFX_PASSWORD) { - $nativeTwoSign = @("sign", "/fd", "SHA256", "/f", $env:SIGNTOOL_RS_TEST_PFX, "/p", $env:SIGNTOOL_RS_TEST_PFX_PASSWORD, $tmpSignA, $tmpSignB) + $nativeTwoSign = @("sign", "/fd", "SHA256", "/f", $env:PSIGN_TEST_PFX, $tmpSignA, $tmpSignB) + if ($env:PSIGN_TEST_PFX_PASSWORD) { + $nativeTwoSign = @("sign", "/fd", "SHA256", "/f", $env:PSIGN_TEST_PFX, "/p", $env:PSIGN_TEST_PFX_PASSWORD, $tmpSignA, $tmpSignB) } & "$nativeSignTool" @nativeTwoSign 2>&1 | Out-Null $nativeTwoSignExit = $LASTEXITCODE @@ -567,18 +567,18 @@ if ($env:SIGNTOOL_RS_UNSIGNED_FIXTURE -and $env:SIGNTOOL_RS_TEST_PFX) { } # PE (.exe/.dll/…): same SIP as native; two copies of CI unsigned PE — SHA256 match or verify-valid semantic match. - $peExt = [System.IO.Path]::GetExtension($env:SIGNTOOL_RS_UNSIGNED_FIXTURE) + $peExt = [System.IO.Path]::GetExtension($env:PSIGN_UNSIGNED_FIXTURE) if ([string]::IsNullOrEmpty($peExt)) { $peExt = ".exe" } $tmpPeNat = Join-Path $env:TEMP ("psign_pe_fixture_native" + $peExt) $tmpPeRust = Join-Path $env:TEMP ("psign_pe_fixture_rust" + $peExt) - Copy-Item -LiteralPath $env:SIGNTOOL_RS_UNSIGNED_FIXTURE -Destination $tmpPeNat -Force - Copy-Item -LiteralPath $env:SIGNTOOL_RS_UNSIGNED_FIXTURE -Destination $tmpPeRust -Force + Copy-Item -LiteralPath $env:PSIGN_UNSIGNED_FIXTURE -Destination $tmpPeNat -Force + Copy-Item -LiteralPath $env:PSIGN_UNSIGNED_FIXTURE -Destination $tmpPeRust -Force $savedPe = $ErrorActionPreference $ErrorActionPreference = "Continue" - $nativePeSign = @("sign", "/fd", "SHA256", "/f", $env:SIGNTOOL_RS_TEST_PFX, $tmpPeNat) + $nativePeSign = @("sign", "/fd", "SHA256", "/f", $env:PSIGN_TEST_PFX, $tmpPeNat) $rustPeSign = @("sign") + (Get-RustSignCredentialArgs) + @("--digest", "sha256", $tmpPeRust) - if ($env:SIGNTOOL_RS_TEST_PFX_PASSWORD) { - $nativePeSign = @("sign", "/fd", "SHA256", "/f", $env:SIGNTOOL_RS_TEST_PFX, "/p", $env:SIGNTOOL_RS_TEST_PFX_PASSWORD, $tmpPeNat) + if ($env:PSIGN_TEST_PFX_PASSWORD) { + $nativePeSign = @("sign", "/fd", "SHA256", "/f", $env:PSIGN_TEST_PFX, "/p", $env:PSIGN_TEST_PFX_PASSWORD, $tmpPeNat) } & "$nativeSignTool" @nativePeSign 2>&1 | Out-Null $nativePeSignExit = $LASTEXITCODE @@ -617,14 +617,14 @@ if ($env:SIGNTOOL_RS_UNSIGNED_FIXTURE -and $env:SIGNTOOL_RS_TEST_PFX) { # Sign with /d + /du then verify /pa /v /d: Authenticode program name + URL must match native output lines. $tmpDesc = Join-Path $env:TEMP "psign_verify_desc.exe" - Copy-Item -LiteralPath $env:SIGNTOOL_RS_UNSIGNED_FIXTURE -Destination $tmpDesc -Force + Copy-Item -LiteralPath $env:PSIGN_UNSIGNED_FIXTURE -Destination $tmpDesc -Force $parityDesc = "psign_parity_desc_2026" $parityUrl = "https://example.invalid/psign-parity" $savedDesc = $ErrorActionPreference $ErrorActionPreference = "Continue" - $nativeSignDesc = @("sign", "/fd", "SHA256", "/f", $env:SIGNTOOL_RS_TEST_PFX, "/d", $parityDesc, "/du", $parityUrl, $tmpDesc) - if ($env:SIGNTOOL_RS_TEST_PFX_PASSWORD) { - $nativeSignDesc = @("sign", "/fd", "SHA256", "/f", $env:SIGNTOOL_RS_TEST_PFX, "/p", $env:SIGNTOOL_RS_TEST_PFX_PASSWORD, "/d", $parityDesc, "/du", $parityUrl, $tmpDesc) + $nativeSignDesc = @("sign", "/fd", "SHA256", "/f", $env:PSIGN_TEST_PFX, "/d", $parityDesc, "/du", $parityUrl, $tmpDesc) + if ($env:PSIGN_TEST_PFX_PASSWORD) { + $nativeSignDesc = @("sign", "/fd", "SHA256", "/f", $env:PSIGN_TEST_PFX, "/p", $env:PSIGN_TEST_PFX_PASSWORD, "/d", $parityDesc, "/du", $parityUrl, $tmpDesc) } & "$nativeSignTool" @nativeSignDesc 2>&1 | Out-Null $nativeSignDescExit = $LASTEXITCODE @@ -667,7 +667,7 @@ if ($env:SIGNTOOL_RS_UNSIGNED_FIXTURE -and $env:SIGNTOOL_RS_TEST_PFX) { } # PowerShell .ps1: Windows CryptSIP (same SignerSignEx3 / WinVerifyTrust stack as native signtool). - $ps1Src = if ($env:SIGNTOOL_RS_PS1_UNSIGNED_FIXTURE) { $env:SIGNTOOL_RS_PS1_UNSIGNED_FIXTURE } else { Join-Path $workspace "tests\fixtures\unsigned-sample.ps1" } + $ps1Src = if ($env:PSIGN_PS1_UNSIGNED_FIXTURE) { $env:PSIGN_PS1_UNSIGNED_FIXTURE } else { Join-Path $workspace "tests\fixtures\unsigned-sample.ps1" } if (Test-Path -LiteralPath $ps1Src) { $expectedScenarioIds += @("sign_ps1_sha256_match_native", "verify_ps1_pa_exit_match") $tmpPs1Nat = Join-Path $env:TEMP "psign_ps1_native.ps1" @@ -676,10 +676,10 @@ if ($env:SIGNTOOL_RS_UNSIGNED_FIXTURE -and $env:SIGNTOOL_RS_TEST_PFX) { Copy-Item -LiteralPath $ps1Src -Destination $tmpPs1Rust -Force $savedPs1 = $ErrorActionPreference $ErrorActionPreference = "Continue" - $nativePs1Sign = @("sign", "/fd", "SHA256", "/f", $env:SIGNTOOL_RS_TEST_PFX, $tmpPs1Nat) + $nativePs1Sign = @("sign", "/fd", "SHA256", "/f", $env:PSIGN_TEST_PFX, $tmpPs1Nat) $rustPs1Sign = @("sign") + (Get-RustSignCredentialArgs) + @("--digest", "sha256", $tmpPs1Rust) - if ($env:SIGNTOOL_RS_TEST_PFX_PASSWORD) { - $nativePs1Sign = @("sign", "/fd", "SHA256", "/f", $env:SIGNTOOL_RS_TEST_PFX, "/p", $env:SIGNTOOL_RS_TEST_PFX_PASSWORD, $tmpPs1Nat) + if ($env:PSIGN_TEST_PFX_PASSWORD) { + $nativePs1Sign = @("sign", "/fd", "SHA256", "/f", $env:PSIGN_TEST_PFX, "/p", $env:PSIGN_TEST_PFX_PASSWORD, $tmpPs1Nat) } & "$nativeSignTool" @nativePs1Sign 2>&1 | Out-Null $nativePs1SignExit = $LASTEXITCODE @@ -727,9 +727,9 @@ if ($env:SIGNTOOL_RS_UNSIGNED_FIXTURE -and $env:SIGNTOOL_RS_TEST_PFX) { Copy-Item -LiteralPath $ps1Src -Destination $tmpPs1DescRust -Force $savedPs1Desc = $ErrorActionPreference $ErrorActionPreference = "Continue" - $nativePs1DescSign = @("sign", "/fd", "SHA256", "/f", $env:SIGNTOOL_RS_TEST_PFX, "/d", $parityDesc, "/du", $parityUrl, $tmpPs1DescNat) - if ($env:SIGNTOOL_RS_TEST_PFX_PASSWORD) { - $nativePs1DescSign = @("sign", "/fd", "SHA256", "/f", $env:SIGNTOOL_RS_TEST_PFX, "/p", $env:SIGNTOOL_RS_TEST_PFX_PASSWORD, "/d", $parityDesc, "/du", $parityUrl, $tmpPs1DescNat) + $nativePs1DescSign = @("sign", "/fd", "SHA256", "/f", $env:PSIGN_TEST_PFX, "/d", $parityDesc, "/du", $parityUrl, $tmpPs1DescNat) + if ($env:PSIGN_TEST_PFX_PASSWORD) { + $nativePs1DescSign = @("sign", "/fd", "SHA256", "/f", $env:PSIGN_TEST_PFX, "/p", $env:PSIGN_TEST_PFX_PASSWORD, "/d", $parityDesc, "/du", $parityUrl, $tmpPs1DescNat) } & "$nativeSignTool" @nativePs1DescSign 2>&1 | Out-Null $nativePs1DescSignExit = $LASTEXITCODE @@ -773,7 +773,7 @@ if ($env:SIGNTOOL_RS_UNSIGNED_FIXTURE -and $env:SIGNTOOL_RS_TEST_PFX) { } # PowerShell module .psm1 (same CryptSIP stack as .ps1). - $psm1Src = if ($env:SIGNTOOL_RS_PSM1_UNSIGNED_FIXTURE) { $env:SIGNTOOL_RS_PSM1_UNSIGNED_FIXTURE } else { Join-Path $workspace "tests\fixtures\unsigned-sample.psm1" } + $psm1Src = if ($env:PSIGN_PSM1_UNSIGNED_FIXTURE) { $env:PSIGN_PSM1_UNSIGNED_FIXTURE } else { Join-Path $workspace "tests\fixtures\unsigned-sample.psm1" } if (Test-Path -LiteralPath $psm1Src) { $expectedScenarioIds += @( "sign_psm1_sha256_match_native", @@ -786,10 +786,10 @@ if ($env:SIGNTOOL_RS_UNSIGNED_FIXTURE -and $env:SIGNTOOL_RS_TEST_PFX) { Copy-Item -LiteralPath $psm1Src -Destination $tmpPsm1Rust -Force $savedPsm1 = $ErrorActionPreference $ErrorActionPreference = "Continue" - $nativePsm1Sign = @("sign", "/fd", "SHA256", "/f", $env:SIGNTOOL_RS_TEST_PFX, $tmpPsm1Nat) + $nativePsm1Sign = @("sign", "/fd", "SHA256", "/f", $env:PSIGN_TEST_PFX, $tmpPsm1Nat) $rustPsm1Sign = @("sign") + (Get-RustSignCredentialArgs) + @("--digest", "sha256", $tmpPsm1Rust) - if ($env:SIGNTOOL_RS_TEST_PFX_PASSWORD) { - $nativePsm1Sign = @("sign", "/fd", "SHA256", "/f", $env:SIGNTOOL_RS_TEST_PFX, "/p", $env:SIGNTOOL_RS_TEST_PFX_PASSWORD, $tmpPsm1Nat) + if ($env:PSIGN_TEST_PFX_PASSWORD) { + $nativePsm1Sign = @("sign", "/fd", "SHA256", "/f", $env:PSIGN_TEST_PFX, "/p", $env:PSIGN_TEST_PFX_PASSWORD, $tmpPsm1Nat) } & "$nativeSignTool" @nativePsm1Sign 2>&1 | Out-Null $nativePsm1SignExit = $LASTEXITCODE @@ -832,9 +832,9 @@ if ($env:SIGNTOOL_RS_UNSIGNED_FIXTURE -and $env:SIGNTOOL_RS_TEST_PFX) { Copy-Item -LiteralPath $psm1Src -Destination $tmpPsm1DescRust -Force $savedPsm1Desc = $ErrorActionPreference $ErrorActionPreference = "Continue" - $nativePsm1DescSign = @("sign", "/fd", "SHA256", "/f", $env:SIGNTOOL_RS_TEST_PFX, "/d", $parityDesc, "/du", $parityUrl, $tmpPsm1DescNat) - if ($env:SIGNTOOL_RS_TEST_PFX_PASSWORD) { - $nativePsm1DescSign = @("sign", "/fd", "SHA256", "/f", $env:SIGNTOOL_RS_TEST_PFX, "/p", $env:SIGNTOOL_RS_TEST_PFX_PASSWORD, "/d", $parityDesc, "/du", $parityUrl, $tmpPsm1DescNat) + $nativePsm1DescSign = @("sign", "/fd", "SHA256", "/f", $env:PSIGN_TEST_PFX, "/d", $parityDesc, "/du", $parityUrl, $tmpPsm1DescNat) + if ($env:PSIGN_TEST_PFX_PASSWORD) { + $nativePsm1DescSign = @("sign", "/fd", "SHA256", "/f", $env:PSIGN_TEST_PFX, "/p", $env:PSIGN_TEST_PFX_PASSWORD, "/d", $parityDesc, "/du", $parityUrl, $tmpPsm1DescNat) } & "$nativeSignTool" @nativePsm1DescSign 2>&1 | Out-Null $nativePsm1DescSignExit = $LASTEXITCODE @@ -878,7 +878,7 @@ if ($env:SIGNTOOL_RS_UNSIGNED_FIXTURE -and $env:SIGNTOOL_RS_TEST_PFX) { } # PowerShell manifest .psd1 (same CryptSIP stack as .ps1). - $psd1Src = if ($env:SIGNTOOL_RS_PSD1_UNSIGNED_FIXTURE) { $env:SIGNTOOL_RS_PSD1_UNSIGNED_FIXTURE } else { Join-Path $workspace "tests\fixtures\unsigned-sample.psd1" } + $psd1Src = if ($env:PSIGN_PSD1_UNSIGNED_FIXTURE) { $env:PSIGN_PSD1_UNSIGNED_FIXTURE } else { Join-Path $workspace "tests\fixtures\unsigned-sample.psd1" } if (Test-Path -LiteralPath $psd1Src) { $expectedScenarioIds += @( "sign_psd1_sha256_match_native", @@ -891,10 +891,10 @@ if ($env:SIGNTOOL_RS_UNSIGNED_FIXTURE -and $env:SIGNTOOL_RS_TEST_PFX) { Copy-Item -LiteralPath $psd1Src -Destination $tmpPsd1Rust -Force $savedPsd1 = $ErrorActionPreference $ErrorActionPreference = "Continue" - $nativePsd1Sign = @("sign", "/fd", "SHA256", "/f", $env:SIGNTOOL_RS_TEST_PFX, $tmpPsd1Nat) + $nativePsd1Sign = @("sign", "/fd", "SHA256", "/f", $env:PSIGN_TEST_PFX, $tmpPsd1Nat) $rustPsd1Sign = @("sign") + (Get-RustSignCredentialArgs) + @("--digest", "sha256", $tmpPsd1Rust) - if ($env:SIGNTOOL_RS_TEST_PFX_PASSWORD) { - $nativePsd1Sign = @("sign", "/fd", "SHA256", "/f", $env:SIGNTOOL_RS_TEST_PFX, "/p", $env:SIGNTOOL_RS_TEST_PFX_PASSWORD, $tmpPsd1Nat) + if ($env:PSIGN_TEST_PFX_PASSWORD) { + $nativePsd1Sign = @("sign", "/fd", "SHA256", "/f", $env:PSIGN_TEST_PFX, "/p", $env:PSIGN_TEST_PFX_PASSWORD, $tmpPsd1Nat) } & "$nativeSignTool" @nativePsd1Sign 2>&1 | Out-Null $nativePsd1SignExit = $LASTEXITCODE @@ -937,9 +937,9 @@ if ($env:SIGNTOOL_RS_UNSIGNED_FIXTURE -and $env:SIGNTOOL_RS_TEST_PFX) { Copy-Item -LiteralPath $psd1Src -Destination $tmpPsd1DescRust -Force $savedPsd1Desc = $ErrorActionPreference $ErrorActionPreference = "Continue" - $nativePsd1DescSign = @("sign", "/fd", "SHA256", "/f", $env:SIGNTOOL_RS_TEST_PFX, "/d", $parityDesc, "/du", $parityUrl, $tmpPsd1DescNat) - if ($env:SIGNTOOL_RS_TEST_PFX_PASSWORD) { - $nativePsd1DescSign = @("sign", "/fd", "SHA256", "/f", $env:SIGNTOOL_RS_TEST_PFX, "/p", $env:SIGNTOOL_RS_TEST_PFX_PASSWORD, "/d", $parityDesc, "/du", $parityUrl, $tmpPsd1DescNat) + $nativePsd1DescSign = @("sign", "/fd", "SHA256", "/f", $env:PSIGN_TEST_PFX, "/d", $parityDesc, "/du", $parityUrl, $tmpPsd1DescNat) + if ($env:PSIGN_TEST_PFX_PASSWORD) { + $nativePsd1DescSign = @("sign", "/fd", "SHA256", "/f", $env:PSIGN_TEST_PFX, "/p", $env:PSIGN_TEST_PFX_PASSWORD, "/d", $parityDesc, "/du", $parityUrl, $tmpPsd1DescNat) } & "$nativeSignTool" @nativePsd1DescSign 2>&1 | Out-Null $nativePsd1DescSignExit = $LASTEXITCODE @@ -983,7 +983,7 @@ if ($env:SIGNTOOL_RS_UNSIGNED_FIXTURE -and $env:SIGNTOOL_RS_TEST_PFX) { } # Windows Script Host .js: OS CryptSIP for script signing (same SignerSignEx3 / WinVerifyTrust as native). - $jsSrc = if ($env:SIGNTOOL_RS_JS_UNSIGNED_FIXTURE) { $env:SIGNTOOL_RS_JS_UNSIGNED_FIXTURE } else { Join-Path $workspace "tests\fixtures\unsigned-sample.js" } + $jsSrc = if ($env:PSIGN_JS_UNSIGNED_FIXTURE) { $env:PSIGN_JS_UNSIGNED_FIXTURE } else { Join-Path $workspace "tests\fixtures\unsigned-sample.js" } if (Test-Path -LiteralPath $jsSrc) { $expectedScenarioIds += @( "sign_js_sha256_match_native", @@ -996,10 +996,10 @@ if ($env:SIGNTOOL_RS_UNSIGNED_FIXTURE -and $env:SIGNTOOL_RS_TEST_PFX) { Copy-Item -LiteralPath $jsSrc -Destination $tmpJsRust -Force $savedJs = $ErrorActionPreference $ErrorActionPreference = "Continue" - $nativeJsSign = @("sign", "/fd", "SHA256", "/f", $env:SIGNTOOL_RS_TEST_PFX, $tmpJsNat) + $nativeJsSign = @("sign", "/fd", "SHA256", "/f", $env:PSIGN_TEST_PFX, $tmpJsNat) $rustJsSign = @("sign") + (Get-RustSignCredentialArgs) + @("--digest", "sha256", $tmpJsRust) - if ($env:SIGNTOOL_RS_TEST_PFX_PASSWORD) { - $nativeJsSign = @("sign", "/fd", "SHA256", "/f", $env:SIGNTOOL_RS_TEST_PFX, "/p", $env:SIGNTOOL_RS_TEST_PFX_PASSWORD, $tmpJsNat) + if ($env:PSIGN_TEST_PFX_PASSWORD) { + $nativeJsSign = @("sign", "/fd", "SHA256", "/f", $env:PSIGN_TEST_PFX, "/p", $env:PSIGN_TEST_PFX_PASSWORD, $tmpJsNat) } & "$nativeSignTool" @nativeJsSign 2>&1 | Out-Null $nativeJsSignExit = $LASTEXITCODE @@ -1042,9 +1042,9 @@ if ($env:SIGNTOOL_RS_UNSIGNED_FIXTURE -and $env:SIGNTOOL_RS_TEST_PFX) { Copy-Item -LiteralPath $jsSrc -Destination $tmpJsDescRust -Force $savedJsDesc = $ErrorActionPreference $ErrorActionPreference = "Continue" - $nativeJsDescSign = @("sign", "/fd", "SHA256", "/f", $env:SIGNTOOL_RS_TEST_PFX, "/d", $parityDesc, "/du", $parityUrl, $tmpJsDescNat) - if ($env:SIGNTOOL_RS_TEST_PFX_PASSWORD) { - $nativeJsDescSign = @("sign", "/fd", "SHA256", "/f", $env:SIGNTOOL_RS_TEST_PFX, "/p", $env:SIGNTOOL_RS_TEST_PFX_PASSWORD, "/d", $parityDesc, "/du", $parityUrl, $tmpJsDescNat) + $nativeJsDescSign = @("sign", "/fd", "SHA256", "/f", $env:PSIGN_TEST_PFX, "/d", $parityDesc, "/du", $parityUrl, $tmpJsDescNat) + if ($env:PSIGN_TEST_PFX_PASSWORD) { + $nativeJsDescSign = @("sign", "/fd", "SHA256", "/f", $env:PSIGN_TEST_PFX, "/p", $env:PSIGN_TEST_PFX_PASSWORD, "/d", $parityDesc, "/du", $parityUrl, $tmpJsDescNat) } & "$nativeSignTool" @nativeJsDescSign 2>&1 | Out-Null $nativeJsDescSignExit = $LASTEXITCODE @@ -1088,7 +1088,7 @@ if ($env:SIGNTOOL_RS_UNSIGNED_FIXTURE -and $env:SIGNTOOL_RS_TEST_PFX) { } # Windows Script Host .vbs (same stack as .js when SIP registered). - $vbsSrc = if ($env:SIGNTOOL_RS_VBS_UNSIGNED_FIXTURE) { $env:SIGNTOOL_RS_VBS_UNSIGNED_FIXTURE } else { Join-Path $workspace "tests\fixtures\unsigned-sample.vbs" } + $vbsSrc = if ($env:PSIGN_VBS_UNSIGNED_FIXTURE) { $env:PSIGN_VBS_UNSIGNED_FIXTURE } else { Join-Path $workspace "tests\fixtures\unsigned-sample.vbs" } if (Test-Path -LiteralPath $vbsSrc) { $expectedScenarioIds += @( "sign_vbs_sha256_match_native", @@ -1101,10 +1101,10 @@ if ($env:SIGNTOOL_RS_UNSIGNED_FIXTURE -and $env:SIGNTOOL_RS_TEST_PFX) { Copy-Item -LiteralPath $vbsSrc -Destination $tmpVbsRust -Force $savedVbs = $ErrorActionPreference $ErrorActionPreference = "Continue" - $nativeVbsSign = @("sign", "/fd", "SHA256", "/f", $env:SIGNTOOL_RS_TEST_PFX, $tmpVbsNat) + $nativeVbsSign = @("sign", "/fd", "SHA256", "/f", $env:PSIGN_TEST_PFX, $tmpVbsNat) $rustVbsSign = @("sign") + (Get-RustSignCredentialArgs) + @("--digest", "sha256", $tmpVbsRust) - if ($env:SIGNTOOL_RS_TEST_PFX_PASSWORD) { - $nativeVbsSign = @("sign", "/fd", "SHA256", "/f", $env:SIGNTOOL_RS_TEST_PFX, "/p", $env:SIGNTOOL_RS_TEST_PFX_PASSWORD, $tmpVbsNat) + if ($env:PSIGN_TEST_PFX_PASSWORD) { + $nativeVbsSign = @("sign", "/fd", "SHA256", "/f", $env:PSIGN_TEST_PFX, "/p", $env:PSIGN_TEST_PFX_PASSWORD, $tmpVbsNat) } & "$nativeSignTool" @nativeVbsSign 2>&1 | Out-Null $nativeVbsSignExit = $LASTEXITCODE @@ -1147,9 +1147,9 @@ if ($env:SIGNTOOL_RS_UNSIGNED_FIXTURE -and $env:SIGNTOOL_RS_TEST_PFX) { Copy-Item -LiteralPath $vbsSrc -Destination $tmpVbsDescRust -Force $savedVbsDesc = $ErrorActionPreference $ErrorActionPreference = "Continue" - $nativeVbsDescSign = @("sign", "/fd", "SHA256", "/f", $env:SIGNTOOL_RS_TEST_PFX, "/d", $parityDesc, "/du", $parityUrl, $tmpVbsDescNat) - if ($env:SIGNTOOL_RS_TEST_PFX_PASSWORD) { - $nativeVbsDescSign = @("sign", "/fd", "SHA256", "/f", $env:SIGNTOOL_RS_TEST_PFX, "/p", $env:SIGNTOOL_RS_TEST_PFX_PASSWORD, "/d", $parityDesc, "/du", $parityUrl, $tmpVbsDescNat) + $nativeVbsDescSign = @("sign", "/fd", "SHA256", "/f", $env:PSIGN_TEST_PFX, "/d", $parityDesc, "/du", $parityUrl, $tmpVbsDescNat) + if ($env:PSIGN_TEST_PFX_PASSWORD) { + $nativeVbsDescSign = @("sign", "/fd", "SHA256", "/f", $env:PSIGN_TEST_PFX, "/p", $env:PSIGN_TEST_PFX_PASSWORD, "/d", $parityDesc, "/du", $parityUrl, $tmpVbsDescNat) } & "$nativeSignTool" @nativeVbsDescSign 2>&1 | Out-Null $nativeVbsDescSignExit = $LASTEXITCODE @@ -1193,7 +1193,7 @@ if ($env:SIGNTOOL_RS_UNSIGNED_FIXTURE -and $env:SIGNTOOL_RS_TEST_PFX) { } # Windows Script File .wsf (XML container — OS SIP when registered). - $wsfSrc = if ($env:SIGNTOOL_RS_WSF_UNSIGNED_FIXTURE) { $env:SIGNTOOL_RS_WSF_UNSIGNED_FIXTURE } else { Join-Path $workspace "tests\fixtures\unsigned-sample.wsf" } + $wsfSrc = if ($env:PSIGN_WSF_UNSIGNED_FIXTURE) { $env:PSIGN_WSF_UNSIGNED_FIXTURE } else { Join-Path $workspace "tests\fixtures\unsigned-sample.wsf" } if (Test-Path -LiteralPath $wsfSrc) { $expectedScenarioIds += @( "sign_wsf_sha256_match_native", @@ -1206,10 +1206,10 @@ if ($env:SIGNTOOL_RS_UNSIGNED_FIXTURE -and $env:SIGNTOOL_RS_TEST_PFX) { Copy-Item -LiteralPath $wsfSrc -Destination $tmpWsfRust -Force $savedWsf = $ErrorActionPreference $ErrorActionPreference = "Continue" - $nativeWsfSign = @("sign", "/fd", "SHA256", "/f", $env:SIGNTOOL_RS_TEST_PFX, $tmpWsfNat) + $nativeWsfSign = @("sign", "/fd", "SHA256", "/f", $env:PSIGN_TEST_PFX, $tmpWsfNat) $rustWsfSign = @("sign") + (Get-RustSignCredentialArgs) + @("--digest", "sha256", $tmpWsfRust) - if ($env:SIGNTOOL_RS_TEST_PFX_PASSWORD) { - $nativeWsfSign = @("sign", "/fd", "SHA256", "/f", $env:SIGNTOOL_RS_TEST_PFX, "/p", $env:SIGNTOOL_RS_TEST_PFX_PASSWORD, $tmpWsfNat) + if ($env:PSIGN_TEST_PFX_PASSWORD) { + $nativeWsfSign = @("sign", "/fd", "SHA256", "/f", $env:PSIGN_TEST_PFX, "/p", $env:PSIGN_TEST_PFX_PASSWORD, $tmpWsfNat) } & "$nativeSignTool" @nativeWsfSign 2>&1 | Out-Null $nativeWsfSignExit = $LASTEXITCODE @@ -1252,9 +1252,9 @@ if ($env:SIGNTOOL_RS_UNSIGNED_FIXTURE -and $env:SIGNTOOL_RS_TEST_PFX) { Copy-Item -LiteralPath $wsfSrc -Destination $tmpWsfDescRust -Force $savedWsfDesc = $ErrorActionPreference $ErrorActionPreference = "Continue" - $nativeWsfDescSign = @("sign", "/fd", "SHA256", "/f", $env:SIGNTOOL_RS_TEST_PFX, "/d", $parityDesc, "/du", $parityUrl, $tmpWsfDescNat) - if ($env:SIGNTOOL_RS_TEST_PFX_PASSWORD) { - $nativeWsfDescSign = @("sign", "/fd", "SHA256", "/f", $env:SIGNTOOL_RS_TEST_PFX, "/p", $env:SIGNTOOL_RS_TEST_PFX_PASSWORD, "/d", $parityDesc, "/du", $parityUrl, $tmpWsfDescNat) + $nativeWsfDescSign = @("sign", "/fd", "SHA256", "/f", $env:PSIGN_TEST_PFX, "/d", $parityDesc, "/du", $parityUrl, $tmpWsfDescNat) + if ($env:PSIGN_TEST_PFX_PASSWORD) { + $nativeWsfDescSign = @("sign", "/fd", "SHA256", "/f", $env:PSIGN_TEST_PFX, "/p", $env:PSIGN_TEST_PFX_PASSWORD, "/d", $parityDesc, "/du", $parityUrl, $tmpWsfDescNat) } & "$nativeSignTool" @nativeWsfDescSign 2>&1 | Out-Null $nativeWsfDescSignExit = $LASTEXITCODE @@ -1299,23 +1299,23 @@ if ($env:SIGNTOOL_RS_UNSIGNED_FIXTURE -and $env:SIGNTOOL_RS_TEST_PFX) { } # MSIX semantic scenario: native and rust sign with RFC3161 should align on success/failure. -if ($env:SIGNTOOL_RS_MSIX_UNSIGNED_FIXTURE -and $env:SIGNTOOL_RS_MSIX_TEST_PFX -and $env:SIGNTOOL_RS_MSIX_TIMESTAMP_URL) { +if ($env:PSIGN_MSIX_UNSIGNED_FIXTURE -and $env:PSIGN_MSIX_TEST_PFX -and $env:PSIGN_MSIX_TIMESTAMP_URL) { $expectedScenarioIds += @("artifact_msix_sign_semantic", "artifact_verify_msix_print_description_match") $tempNativeMsix = Join-Path $env:TEMP "psign_ci_msix_native.msix" $tempRustMsix = Join-Path $env:TEMP "psign_ci_msix_rust.msix" - Copy-Item -LiteralPath $env:SIGNTOOL_RS_MSIX_UNSIGNED_FIXTURE -Destination $tempNativeMsix -Force - Copy-Item -LiteralPath $env:SIGNTOOL_RS_MSIX_UNSIGNED_FIXTURE -Destination $tempRustMsix -Force + Copy-Item -LiteralPath $env:PSIGN_MSIX_UNSIGNED_FIXTURE -Destination $tempNativeMsix -Force + Copy-Item -LiteralPath $env:PSIGN_MSIX_UNSIGNED_FIXTURE -Destination $tempRustMsix -Force - $nativeSignArgs = @("sign", "/fd", "SHA256", "/f", $env:SIGNTOOL_RS_MSIX_TEST_PFX, "/tr", $env:SIGNTOOL_RS_MSIX_TIMESTAMP_URL, "/td", "SHA256", $tempNativeMsix) - if ($env:SIGNTOOL_RS_MSIX_TEST_PFX_PASSWORD) { - $nativeSignArgs = @("sign", "/fd", "SHA256", "/f", $env:SIGNTOOL_RS_MSIX_TEST_PFX, "/p", $env:SIGNTOOL_RS_MSIX_TEST_PFX_PASSWORD, "/tr", $env:SIGNTOOL_RS_MSIX_TIMESTAMP_URL, "/td", "SHA256", $tempNativeMsix) + $nativeSignArgs = @("sign", "/fd", "SHA256", "/f", $env:PSIGN_MSIX_TEST_PFX, "/tr", $env:PSIGN_MSIX_TIMESTAMP_URL, "/td", "SHA256", $tempNativeMsix) + if ($env:PSIGN_MSIX_TEST_PFX_PASSWORD) { + $nativeSignArgs = @("sign", "/fd", "SHA256", "/f", $env:PSIGN_MSIX_TEST_PFX, "/p", $env:PSIGN_MSIX_TEST_PFX_PASSWORD, "/tr", $env:PSIGN_MSIX_TIMESTAMP_URL, "/td", "SHA256", $tempNativeMsix) } $saved = $ErrorActionPreference $ErrorActionPreference = "Continue" & "$nativeSignTool" @nativeSignArgs 2>&1 | Out-Null $nativeMsixExit = $LASTEXITCODE - $rustSignArgs = @("sign") + (Get-RustMsixCredentialArgs) + @("--digest", "sha256", "--timestamp-url", $env:SIGNTOOL_RS_MSIX_TIMESTAMP_URL, "--timestamp-digest", "sha256", $tempRustMsix) + $rustSignArgs = @("sign") + (Get-RustMsixCredentialArgs) + @("--digest", "sha256", "--timestamp-url", $env:PSIGN_MSIX_TIMESTAMP_URL, "--timestamp-digest", "sha256", $tempRustMsix) & "$rustBin" @rustSignArgs 2>&1 | Out-Null $rustMsixExit = $LASTEXITCODE $ErrorActionPreference = $saved @@ -1342,21 +1342,21 @@ if ($env:SIGNTOOL_RS_MSIX_UNSIGNED_FIXTURE -and $env:SIGNTOOL_RS_MSIX_TEST_PFX - $msixParityUrl = "https://example.invalid/psign-parity" $tempNativeMsixDesc = Join-Path $env:TEMP "psign_ci_msix_native_desc.msix" $tempRustMsixDesc = Join-Path $env:TEMP "psign_ci_msix_rust_desc.msix" - Copy-Item -LiteralPath $env:SIGNTOOL_RS_MSIX_UNSIGNED_FIXTURE -Destination $tempNativeMsixDesc -Force - Copy-Item -LiteralPath $env:SIGNTOOL_RS_MSIX_UNSIGNED_FIXTURE -Destination $tempRustMsixDesc -Force + Copy-Item -LiteralPath $env:PSIGN_MSIX_UNSIGNED_FIXTURE -Destination $tempNativeMsixDesc -Force + Copy-Item -LiteralPath $env:PSIGN_MSIX_UNSIGNED_FIXTURE -Destination $tempRustMsixDesc -Force $savedMsixDesc = $ErrorActionPreference $ErrorActionPreference = "Continue" $nativeMsixDescSign = @( - "sign", "/fd", "SHA256", "/f", $env:SIGNTOOL_RS_MSIX_TEST_PFX, + "sign", "/fd", "SHA256", "/f", $env:PSIGN_MSIX_TEST_PFX, "/d", $msixParityDesc, "/du", $msixParityUrl, - "/tr", $env:SIGNTOOL_RS_MSIX_TIMESTAMP_URL, "/td", "SHA256", + "/tr", $env:PSIGN_MSIX_TIMESTAMP_URL, "/td", "SHA256", $tempNativeMsixDesc ) - if ($env:SIGNTOOL_RS_MSIX_TEST_PFX_PASSWORD) { + if ($env:PSIGN_MSIX_TEST_PFX_PASSWORD) { $nativeMsixDescSign = @( - "sign", "/fd", "SHA256", "/f", $env:SIGNTOOL_RS_MSIX_TEST_PFX, "/p", $env:SIGNTOOL_RS_MSIX_TEST_PFX_PASSWORD, + "sign", "/fd", "SHA256", "/f", $env:PSIGN_MSIX_TEST_PFX, "/p", $env:PSIGN_MSIX_TEST_PFX_PASSWORD, "/d", $msixParityDesc, "/du", $msixParityUrl, - "/tr", $env:SIGNTOOL_RS_MSIX_TIMESTAMP_URL, "/td", "SHA256", + "/tr", $env:PSIGN_MSIX_TIMESTAMP_URL, "/td", "SHA256", $tempNativeMsixDesc ) } @@ -1366,7 +1366,7 @@ if ($env:SIGNTOOL_RS_MSIX_UNSIGNED_FIXTURE -and $env:SIGNTOOL_RS_MSIX_TEST_PFX - $rustMsixDescSign = @("sign") + (Get-RustMsixCredentialArgs) + @( "--digest", "sha256", "--description", $msixParityDesc, "--description-url", $msixParityUrl, - "--timestamp-url", $env:SIGNTOOL_RS_MSIX_TIMESTAMP_URL, "--timestamp-digest", "sha256", + "--timestamp-url", $env:PSIGN_MSIX_TIMESTAMP_URL, "--timestamp-digest", "sha256", $tempRustMsixDesc ) & "$rustBin" @rustMsixDescSign 2>&1 | Out-Null @@ -1409,23 +1409,23 @@ if ($env:SIGNTOOL_RS_MSIX_UNSIGNED_FIXTURE -and $env:SIGNTOOL_RS_MSIX_TEST_PFX - } # MSIX decoupled digest scenario: rust dlib/dmdf bridge must align with native success/failure. -if ($env:SIGNTOOL_RS_MSIX_UNSIGNED_FIXTURE -and $env:SIGNTOOL_RS_MSIX_TEST_PFX -and $env:SIGNTOOL_RS_MSIX_TIMESTAMP_URL -and $env:SIGNTOOL_RS_MSIX_DLIB -and $env:SIGNTOOL_RS_MSIX_DMDF) { +if ($env:PSIGN_MSIX_UNSIGNED_FIXTURE -and $env:PSIGN_MSIX_TEST_PFX -and $env:PSIGN_MSIX_TIMESTAMP_URL -and $env:PSIGN_MSIX_DLIB -and $env:PSIGN_MSIX_DMDF) { $expectedScenarioIds += @("artifact_msix_decoupled_semantic") $tempNativeMsixDecoupled = Join-Path $env:TEMP "psign_ci_msix_native_decoupled.msix" $tempRustMsixDecoupled = Join-Path $env:TEMP "psign_ci_msix_rust_decoupled.msix" - Copy-Item -LiteralPath $env:SIGNTOOL_RS_MSIX_UNSIGNED_FIXTURE -Destination $tempNativeMsixDecoupled -Force - Copy-Item -LiteralPath $env:SIGNTOOL_RS_MSIX_UNSIGNED_FIXTURE -Destination $tempRustMsixDecoupled -Force + Copy-Item -LiteralPath $env:PSIGN_MSIX_UNSIGNED_FIXTURE -Destination $tempNativeMsixDecoupled -Force + Copy-Item -LiteralPath $env:PSIGN_MSIX_UNSIGNED_FIXTURE -Destination $tempRustMsixDecoupled -Force - $nativeDecoupledArgs = @("sign", "/fd", "SHA256", "/f", $env:SIGNTOOL_RS_MSIX_TEST_PFX, "/tr", $env:SIGNTOOL_RS_MSIX_TIMESTAMP_URL, "/td", "SHA256", "/dlib", $env:SIGNTOOL_RS_MSIX_DLIB, "/dmdf", $env:SIGNTOOL_RS_MSIX_DMDF, "/ph", $tempNativeMsixDecoupled) - if ($env:SIGNTOOL_RS_MSIX_TEST_PFX_PASSWORD) { - $nativeDecoupledArgs = @("sign", "/fd", "SHA256", "/f", $env:SIGNTOOL_RS_MSIX_TEST_PFX, "/p", $env:SIGNTOOL_RS_MSIX_TEST_PFX_PASSWORD, "/tr", $env:SIGNTOOL_RS_MSIX_TIMESTAMP_URL, "/td", "SHA256", "/dlib", $env:SIGNTOOL_RS_MSIX_DLIB, "/dmdf", $env:SIGNTOOL_RS_MSIX_DMDF, "/ph", $tempNativeMsixDecoupled) + $nativeDecoupledArgs = @("sign", "/fd", "SHA256", "/f", $env:PSIGN_MSIX_TEST_PFX, "/tr", $env:PSIGN_MSIX_TIMESTAMP_URL, "/td", "SHA256", "/dlib", $env:PSIGN_MSIX_DLIB, "/dmdf", $env:PSIGN_MSIX_DMDF, "/ph", $tempNativeMsixDecoupled) + if ($env:PSIGN_MSIX_TEST_PFX_PASSWORD) { + $nativeDecoupledArgs = @("sign", "/fd", "SHA256", "/f", $env:PSIGN_MSIX_TEST_PFX, "/p", $env:PSIGN_MSIX_TEST_PFX_PASSWORD, "/tr", $env:PSIGN_MSIX_TIMESTAMP_URL, "/td", "SHA256", "/dlib", $env:PSIGN_MSIX_DLIB, "/dmdf", $env:PSIGN_MSIX_DMDF, "/ph", $tempNativeMsixDecoupled) } $saved = $ErrorActionPreference $ErrorActionPreference = "Continue" & "$nativeSignTool" @nativeDecoupledArgs 2>&1 | Out-Null $nativeDecoupledExit = $LASTEXITCODE - $rustDecoupledArgs = @("sign") + (Get-RustMsixCredentialArgs) + @("--digest", "sha256", "--timestamp-url", $env:SIGNTOOL_RS_MSIX_TIMESTAMP_URL, "--timestamp-digest", "sha256", "--dlib", $env:SIGNTOOL_RS_MSIX_DLIB, "--dmdf", $env:SIGNTOOL_RS_MSIX_DMDF, "--page-hashes", $tempRustMsixDecoupled) + $rustDecoupledArgs = @("sign") + (Get-RustMsixCredentialArgs) + @("--digest", "sha256", "--timestamp-url", $env:PSIGN_MSIX_TIMESTAMP_URL, "--timestamp-digest", "sha256", "--dlib", $env:PSIGN_MSIX_DLIB, "--dmdf", $env:PSIGN_MSIX_DMDF, "--page-hashes", $tempRustMsixDecoupled) & "$rustBin" @rustDecoupledArgs 2>&1 | Out-Null $rustDecoupledExit = $LASTEXITCODE $ErrorActionPreference = $saved @@ -1447,10 +1447,10 @@ if ($env:SIGNTOOL_RS_MSIX_UNSIGNED_FIXTURE -and $env:SIGNTOOL_RS_MSIX_TEST_PFX - } } -# Optional WinMD (Windows metadata, PE-based CLI assembly SIP): `SIGNTOOL_RS_WINMD_UNSIGNED_FIXTURE` + `SIGNTOOL_RS_TEST_PFX`. -# Optional RFC3161: `SIGNTOOL_RS_WINMD_TIMESTAMP_URL`. Smoke helper: `scripts/sip-format-smoke.ps1`. -$winmdSrc = $env:SIGNTOOL_RS_WINMD_UNSIGNED_FIXTURE -if ($winmdSrc -and $env:SIGNTOOL_RS_TEST_PFX -and (Test-Path -LiteralPath $winmdSrc)) { +# Optional WinMD (Windows metadata, PE-based CLI assembly SIP): `PSIGN_WINMD_UNSIGNED_FIXTURE` + `PSIGN_TEST_PFX`. +# Optional RFC3161: `PSIGN_WINMD_TIMESTAMP_URL`. Smoke helper: `scripts/sip-format-smoke.ps1`. +$winmdSrc = $env:PSIGN_WINMD_UNSIGNED_FIXTURE +if ($winmdSrc -and $env:PSIGN_TEST_PFX -and (Test-Path -LiteralPath $winmdSrc)) { $expectedScenarioIds += @( "sign_winmd_sha256_match_native", "verify_winmd_pa_exit_match", @@ -1463,18 +1463,18 @@ if ($winmdSrc -and $env:SIGNTOOL_RS_TEST_PFX -and (Test-Path -LiteralPath $winmd $savedW = $ErrorActionPreference $ErrorActionPreference = "Continue" - $nativeWinmdSign = @("sign", "/fd", "SHA256", "/f", $env:SIGNTOOL_RS_TEST_PFX) - if ($env:SIGNTOOL_RS_TEST_PFX_PASSWORD) { - $nativeWinmdSign += @("/p", $env:SIGNTOOL_RS_TEST_PFX_PASSWORD) + $nativeWinmdSign = @("sign", "/fd", "SHA256", "/f", $env:PSIGN_TEST_PFX) + if ($env:PSIGN_TEST_PFX_PASSWORD) { + $nativeWinmdSign += @("/p", $env:PSIGN_TEST_PFX_PASSWORD) } - if ($env:SIGNTOOL_RS_WINMD_TIMESTAMP_URL) { - $nativeWinmdSign += @("/tr", $env:SIGNTOOL_RS_WINMD_TIMESTAMP_URL, "/td", "SHA256") + if ($env:PSIGN_WINMD_TIMESTAMP_URL) { + $nativeWinmdSign += @("/tr", $env:PSIGN_WINMD_TIMESTAMP_URL, "/td", "SHA256") } $nativeWinmdSign += @($tmpWinmdNat) $rustWinmdSign = @("sign") + (Get-RustSignCredentialArgs) + @("--digest", "sha256") - if ($env:SIGNTOOL_RS_WINMD_TIMESTAMP_URL) { - $rustWinmdSign += @("--timestamp-url", $env:SIGNTOOL_RS_WINMD_TIMESTAMP_URL, "--timestamp-digest", "sha256") + if ($env:PSIGN_WINMD_TIMESTAMP_URL) { + $rustWinmdSign += @("--timestamp-url", $env:PSIGN_WINMD_TIMESTAMP_URL, "--timestamp-digest", "sha256") } $rustWinmdSign += @($tmpWinmdRust) @@ -1499,12 +1499,12 @@ if ($winmdSrc -and $env:SIGNTOOL_RS_TEST_PFX -and (Test-Path -LiteralPath $winmd Copy-Item -LiteralPath $winmdSrc -Destination $tmpWinmdDescNat -Force Copy-Item -LiteralPath $winmdSrc -Destination $tmpWinmdDescRust -Force - $nativeWinmdDescSign = @("sign", "/fd", "SHA256", "/f", $env:SIGNTOOL_RS_TEST_PFX, "/d", $winmdParityDesc, "/du", $winmdParityUrl) - if ($env:SIGNTOOL_RS_TEST_PFX_PASSWORD) { - $nativeWinmdDescSign = @("sign", "/fd", "SHA256", "/f", $env:SIGNTOOL_RS_TEST_PFX, "/p", $env:SIGNTOOL_RS_TEST_PFX_PASSWORD, "/d", $winmdParityDesc, "/du", $winmdParityUrl) + $nativeWinmdDescSign = @("sign", "/fd", "SHA256", "/f", $env:PSIGN_TEST_PFX, "/d", $winmdParityDesc, "/du", $winmdParityUrl) + if ($env:PSIGN_TEST_PFX_PASSWORD) { + $nativeWinmdDescSign = @("sign", "/fd", "SHA256", "/f", $env:PSIGN_TEST_PFX, "/p", $env:PSIGN_TEST_PFX_PASSWORD, "/d", $winmdParityDesc, "/du", $winmdParityUrl) } - if ($env:SIGNTOOL_RS_WINMD_TIMESTAMP_URL) { - $nativeWinmdDescSign += @("/tr", $env:SIGNTOOL_RS_WINMD_TIMESTAMP_URL, "/td", "SHA256") + if ($env:PSIGN_WINMD_TIMESTAMP_URL) { + $nativeWinmdDescSign += @("/tr", $env:PSIGN_WINMD_TIMESTAMP_URL, "/td", "SHA256") } $nativeWinmdDescSign += @($tmpWinmdDescNat) @@ -1512,8 +1512,8 @@ if ($winmdSrc -and $env:SIGNTOOL_RS_TEST_PFX -and (Test-Path -LiteralPath $winmd "--digest", "sha256", "--description", $winmdParityDesc, "--description-url", $winmdParityUrl ) - if ($env:SIGNTOOL_RS_WINMD_TIMESTAMP_URL) { - $rustWinmdDescSign += @("--timestamp-url", $env:SIGNTOOL_RS_WINMD_TIMESTAMP_URL, "--timestamp-digest", "sha256") + if ($env:PSIGN_WINMD_TIMESTAMP_URL) { + $rustWinmdDescSign += @("--timestamp-url", $env:PSIGN_WINMD_TIMESTAMP_URL, "--timestamp-digest", "sha256") } $rustWinmdDescSign += @($tmpWinmdDescRust) @@ -1576,10 +1576,10 @@ if ($winmdSrc -and $env:SIGNTOOL_RS_TEST_PFX -and (Test-Path -LiteralPath $winmd } } -# Optional MSI (Windows Installer SIP): supply `SIGNTOOL_RS_MSI_UNSIGNED_FIXTURE` + same PFX env as PE (`SIGNTOOL_RS_TEST_PFX`). -# Optional RFC3161 during sign: `SIGNTOOL_RS_MSI_TIMESTAMP_URL` (native `/tr` `/td SHA256`, rust `--timestamp-url` `--timestamp-digest sha256`). -$mmsiSrc = $env:SIGNTOOL_RS_MSI_UNSIGNED_FIXTURE -if ($mmsiSrc -and $env:SIGNTOOL_RS_TEST_PFX -and (Test-Path -LiteralPath $mmsiSrc)) { +# Optional MSI (Windows Installer SIP): supply `PSIGN_MSI_UNSIGNED_FIXTURE` + same PFX env as PE (`PSIGN_TEST_PFX`). +# Optional RFC3161 during sign: `PSIGN_MSI_TIMESTAMP_URL` (native `/tr` `/td SHA256`, rust `--timestamp-url` `--timestamp-digest sha256`). +$mmsiSrc = $env:PSIGN_MSI_UNSIGNED_FIXTURE +if ($mmsiSrc -and $env:PSIGN_TEST_PFX -and (Test-Path -LiteralPath $mmsiSrc)) { $expectedScenarioIds += @( "sign_msi_sha256_match_native", "verify_msi_pa_exit_match", @@ -1592,18 +1592,18 @@ if ($mmsiSrc -and $env:SIGNTOOL_RS_TEST_PFX -and (Test-Path -LiteralPath $mmsiSr $savedMsi = $ErrorActionPreference $ErrorActionPreference = "Continue" - $nativeMsiSign = @("sign", "/fd", "SHA256", "/f", $env:SIGNTOOL_RS_TEST_PFX) - if ($env:SIGNTOOL_RS_TEST_PFX_PASSWORD) { - $nativeMsiSign += @("/p", $env:SIGNTOOL_RS_TEST_PFX_PASSWORD) + $nativeMsiSign = @("sign", "/fd", "SHA256", "/f", $env:PSIGN_TEST_PFX) + if ($env:PSIGN_TEST_PFX_PASSWORD) { + $nativeMsiSign += @("/p", $env:PSIGN_TEST_PFX_PASSWORD) } - if ($env:SIGNTOOL_RS_MSI_TIMESTAMP_URL) { - $nativeMsiSign += @("/tr", $env:SIGNTOOL_RS_MSI_TIMESTAMP_URL, "/td", "SHA256") + if ($env:PSIGN_MSI_TIMESTAMP_URL) { + $nativeMsiSign += @("/tr", $env:PSIGN_MSI_TIMESTAMP_URL, "/td", "SHA256") } $nativeMsiSign += @($tmpMsiNat) $rustMsiSign = @("sign") + (Get-RustSignCredentialArgs) + @("--digest", "sha256") - if ($env:SIGNTOOL_RS_MSI_TIMESTAMP_URL) { - $rustMsiSign += @("--timestamp-url", $env:SIGNTOOL_RS_MSI_TIMESTAMP_URL, "--timestamp-digest", "sha256") + if ($env:PSIGN_MSI_TIMESTAMP_URL) { + $rustMsiSign += @("--timestamp-url", $env:PSIGN_MSI_TIMESTAMP_URL, "--timestamp-digest", "sha256") } $rustMsiSign += @($tmpMsiRust) @@ -1628,12 +1628,12 @@ if ($mmsiSrc -and $env:SIGNTOOL_RS_TEST_PFX -and (Test-Path -LiteralPath $mmsiSr Copy-Item -LiteralPath $mmsiSrc -Destination $tmpMsiDescNat -Force Copy-Item -LiteralPath $mmsiSrc -Destination $tmpMsiDescRust -Force - $nativeMsiDescSign = @("sign", "/fd", "SHA256", "/f", $env:SIGNTOOL_RS_TEST_PFX, "/d", $msiParityDesc, "/du", $msiParityUrl) - if ($env:SIGNTOOL_RS_TEST_PFX_PASSWORD) { - $nativeMsiDescSign = @("sign", "/fd", "SHA256", "/f", $env:SIGNTOOL_RS_TEST_PFX, "/p", $env:SIGNTOOL_RS_TEST_PFX_PASSWORD, "/d", $msiParityDesc, "/du", $msiParityUrl) + $nativeMsiDescSign = @("sign", "/fd", "SHA256", "/f", $env:PSIGN_TEST_PFX, "/d", $msiParityDesc, "/du", $msiParityUrl) + if ($env:PSIGN_TEST_PFX_PASSWORD) { + $nativeMsiDescSign = @("sign", "/fd", "SHA256", "/f", $env:PSIGN_TEST_PFX, "/p", $env:PSIGN_TEST_PFX_PASSWORD, "/d", $msiParityDesc, "/du", $msiParityUrl) } - if ($env:SIGNTOOL_RS_MSI_TIMESTAMP_URL) { - $nativeMsiDescSign += @("/tr", $env:SIGNTOOL_RS_MSI_TIMESTAMP_URL, "/td", "SHA256") + if ($env:PSIGN_MSI_TIMESTAMP_URL) { + $nativeMsiDescSign += @("/tr", $env:PSIGN_MSI_TIMESTAMP_URL, "/td", "SHA256") } $nativeMsiDescSign += @($tmpMsiDescNat) @@ -1641,8 +1641,8 @@ if ($mmsiSrc -and $env:SIGNTOOL_RS_TEST_PFX -and (Test-Path -LiteralPath $mmsiSr "--digest", "sha256", "--description", $msiParityDesc, "--description-url", $msiParityUrl ) - if ($env:SIGNTOOL_RS_MSI_TIMESTAMP_URL) { - $rustMsiDescSign += @("--timestamp-url", $env:SIGNTOOL_RS_MSI_TIMESTAMP_URL, "--timestamp-digest", "sha256") + if ($env:PSIGN_MSI_TIMESTAMP_URL) { + $rustMsiDescSign += @("--timestamp-url", $env:PSIGN_MSI_TIMESTAMP_URL, "--timestamp-digest", "sha256") } $rustMsiDescSign += @($tmpMsiDescRust) @@ -1706,11 +1706,11 @@ if ($mmsiSrc -and $env:SIGNTOOL_RS_TEST_PFX -and (Test-Path -LiteralPath $mmsiSr } # Optional scenario: detached verify path -if ($env:SIGNTOOL_RS_DETACHED_CONTENT -and $env:SIGNTOOL_RS_DETACHED_PKCS7) { +if ($env:PSIGN_DETACHED_CONTENT -and $env:PSIGN_DETACHED_PKCS7) { $expectedScenarioIds += @("artifact_detached_semantic") $saved = $ErrorActionPreference $ErrorActionPreference = "Continue" - & "$rustBin" verify --policy pa --allow-test-root $env:SIGNTOOL_RS_DETACHED_CONTENT --detached-pkcs7 $env:SIGNTOOL_RS_DETACHED_PKCS7 2>&1 | Out-Null + & "$rustBin" verify --policy pa --allow-test-root $env:PSIGN_DETACHED_CONTENT --detached-pkcs7 $env:PSIGN_DETACHED_PKCS7 2>&1 | Out-Null $detachedExit = $LASTEXITCODE $ErrorActionPreference = $saved # Bare CMS SignedData from `signtool /p7` is normalized to PKCS#7 ContentInfo in Rust; remaining failures stay documented. @@ -1724,13 +1724,13 @@ if ($env:SIGNTOOL_RS_DETACHED_CONTENT -and $env:SIGNTOOL_RS_DETACHED_PKCS7) { } # Optional scenario: catalog verify path (+ `/o` WinTrust flag via `--os-version-check` when catalog is used) -if ($env:SIGNTOOL_RS_CATALOG_TARGET -and $env:SIGNTOOL_RS_CATALOG_FILE) { +if ($env:PSIGN_CATALOG_TARGET -and $env:PSIGN_CATALOG_FILE) { $expectedScenarioIds += @("artifact_catalog_semantic", "artifact_catalog_os_version_semantic") $saved = $ErrorActionPreference $ErrorActionPreference = "Continue" - & "$rustBin" verify $env:SIGNTOOL_RS_CATALOG_TARGET --catalog $env:SIGNTOOL_RS_CATALOG_FILE 2>&1 | Out-Null + & "$rustBin" verify $env:PSIGN_CATALOG_TARGET --catalog $env:PSIGN_CATALOG_FILE 2>&1 | Out-Null $catalogExit = $LASTEXITCODE - & "$rustBin" verify $env:SIGNTOOL_RS_CATALOG_TARGET --catalog $env:SIGNTOOL_RS_CATALOG_FILE --os-version-check "386:10.0.26100.0" 2>&1 | Out-Null + & "$rustBin" verify $env:PSIGN_CATALOG_TARGET --catalog $env:PSIGN_CATALOG_FILE --os-version-check "386:10.0.26100.0" 2>&1 | Out-Null $catalogOsExit = $LASTEXITCODE $ErrorActionPreference = $saved $results += [PSCustomObject]@{ diff --git a/scripts/rust-sip-parity-pe.ps1 b/scripts/rust-sip-parity-pe.ps1 index 33d2c8e..2e430a3 100644 --- a/scripts/rust-sip-parity-pe.ps1 +++ b/scripts/rust-sip-parity-pe.ps1 @@ -1,9 +1,9 @@ # Rust SIP PE parity (experimental): native signtool vs psign with `--rust-sip pe`, then mutual verify. -# Requires: SIGNTOOL_RS_UNSIGNED_FIXTURE, SIGNTOOL_RS_TEST_PFX (optional SIGNTOOL_RS_TEST_PFX_PASSWORD). +# Requires: PSIGN_UNSIGNED_FIXTURE, PSIGN_TEST_PFX (optional PSIGN_TEST_PFX_PASSWORD). # # Usage: # ./scripts/rust-sip-parity-pe.ps1 -# ./scripts/rust-sip-parity-pe.ps1 -WorkspaceRoot D:\dev\signtool-rs +# ./scripts/rust-sip-parity-pe.ps1 -WorkspaceRoot D:\\dev\\psign param( [string]$WorkspaceRoot = "" ) @@ -35,25 +35,25 @@ function Test-RsEnvPresent([string]$Name) { return ($null -ne $v -and $v.Trim().Length -gt 0) } -if (-not (Test-RsEnvPresent "SIGNTOOL_RS_UNSIGNED_FIXTURE") -or -not (Test-RsEnvPresent "SIGNTOOL_RS_TEST_PFX")) { - Write-Host "[skip] rust-sip-parity-pe: set SIGNTOOL_RS_UNSIGNED_FIXTURE and SIGNTOOL_RS_TEST_PFX" +if (-not (Test-RsEnvPresent "PSIGN_UNSIGNED_FIXTURE") -or -not (Test-RsEnvPresent "PSIGN_TEST_PFX")) { + Write-Host "[skip] rust-sip-parity-pe: set PSIGN_UNSIGNED_FIXTURE and PSIGN_TEST_PFX" exit 0 } $native = Resolve-NativeSignTool -$rustBin = Join-Path $WorkspaceRoot "target\debug\psign-tool-windows.exe" +$rustBin = Join-Path $WorkspaceRoot "target\debug\psign-tool.exe" if (-not (Test-Path -LiteralPath $rustBin)) { - Write-Host "Building psign-tool-windows (debug)..." - & cargo build -p psign --bin psign-tool-windows | Out-Host + Write-Host "Building psign-tool (debug)..." + & cargo build -p psign --bin psign-tool | Out-Host } -$u = $env:SIGNTOOL_RS_UNSIGNED_FIXTURE -$pfx = $env:SIGNTOOL_RS_TEST_PFX +$u = $env:PSIGN_UNSIGNED_FIXTURE +$pfx = $env:PSIGN_TEST_PFX if (-not (Test-Path -LiteralPath $u)) { - throw "SIGNTOOL_RS_UNSIGNED_FIXTURE not found: $u" + throw "PSIGN_UNSIGNED_FIXTURE not found: $u" } if (-not (Test-Path -LiteralPath $pfx)) { - throw "SIGNTOOL_RS_TEST_PFX not found: $pfx" + throw "PSIGN_TEST_PFX not found: $pfx" } $tmpNative = Join-Path $env:TEMP "psign_rust_sip_native.exe" @@ -63,8 +63,8 @@ Copy-Item -LiteralPath $u -Destination $tmpRust -Force $nativeSign = @("sign", "/fd", "SHA256", "/f", $pfx, $tmpNative) $rustSign = @("sign", "--pfx", $pfx, "--digest", "sha256", "--rust-sip", "pe", $tmpRust) -if (Test-RsEnvPresent "SIGNTOOL_RS_TEST_PFX_PASSWORD") { - $pw = $env:SIGNTOOL_RS_TEST_PFX_PASSWORD +if (Test-RsEnvPresent "PSIGN_TEST_PFX_PASSWORD") { + $pw = $env:PSIGN_TEST_PFX_PASSWORD $nativeSign = @("sign", "/fd", "SHA256", "/f", $pfx, "/p", $pw, $tmpNative) $rustSign = @("sign", "--pfx", $pfx, "--password", $pw, "--digest", "sha256", "--rust-sip", "pe", $tmpRust) } diff --git a/scripts/sip-format-smoke.ps1 b/scripts/sip-format-smoke.ps1 index 14d5bc7..f0f489f 100644 --- a/scripts/sip-format-smoke.ps1 +++ b/scripts/sip-format-smoke.ps1 @@ -1,5 +1,5 @@ # SIP-backed format smoke checklist: native signtool.exe vs psign on the same machine. -# Uses optional SIGNTOOL_RS_* fixtures when set (same variables as scripts/run-parity-diff.ps1). +# Uses optional PSIGN_* fixtures when set (same variables as scripts/run-parity-diff.ps1). # # Format inventory (sign/timestamp/embedded-verify delegate to OS CryptSIP DLLs via SignerSignEx3 / WinVerifyTrust): # PE .exe .dll .sys .ocx .scr .cpl .efi .mui — Image* APIs apply for remove /s @@ -13,7 +13,7 @@ # # Usage: # ./scripts/sip-format-smoke.ps1 -# ./scripts/sip-format-smoke.ps1 -WorkspaceRoot D:\dev\signtool-rs +# ./scripts/sip-format-smoke.ps1 -WorkspaceRoot D:\\dev\\psign param( [string]$WorkspaceRoot = "" ) @@ -46,10 +46,10 @@ function Test-RsEnvPresent([string]$Name) { } $native = Resolve-NativeSignTool -$rustBin = Join-Path $WorkspaceRoot "target\debug\psign-tool-windows.exe" +$rustBin = Join-Path $WorkspaceRoot "target\debug\psign-tool.exe" if (-not (Test-Path -LiteralPath $rustBin)) { - Write-Host "Building psign-tool-windows (debug)..." - & cargo build -p psign --bin psign-tool-windows | Out-Null + Write-Host "Building psign-tool (debug)..." + & cargo build -p psign --bin psign-tool | Out-Null } Write-Host "Native: $native" @@ -75,42 +75,42 @@ Copy-Item -LiteralPath $native -Destination $tmpPe -Force Invoke-Pair "verify_pa_pe" @("verify", "/pa", $tmpPe) @("verify", "--policy", "pa", $tmpPe) Remove-Item -LiteralPath $tmpPe -Force -ErrorAction SilentlyContinue -if ((Test-RsEnvPresent "SIGNTOOL_RS_UNSIGNED_FIXTURE") -and (Test-RsEnvPresent "SIGNTOOL_RS_TEST_PFX")) { - $u = $env:SIGNTOOL_RS_UNSIGNED_FIXTURE - $pfx = $env:SIGNTOOL_RS_TEST_PFX +if ((Test-RsEnvPresent "PSIGN_UNSIGNED_FIXTURE") -and (Test-RsEnvPresent "PSIGN_TEST_PFX")) { + $u = $env:PSIGN_UNSIGNED_FIXTURE + $pfx = $env:PSIGN_TEST_PFX $tmpSign = Join-Path $env:TEMP "psign_sip_smoke_unsigned.exe" Copy-Item -LiteralPath $u -Destination $tmpSign -Force $nativeSign = @("sign", "/fd", "SHA256", "/f", $pfx, $tmpSign) $rustSign = @("sign", "--pfx", $pfx, "--digest", "sha256", $tmpSign) - if (Test-RsEnvPresent "SIGNTOOL_RS_TEST_PFX_PASSWORD") { - $nativeSign = @("sign", "/fd", "SHA256", "/f", $pfx, "/p", $env:SIGNTOOL_RS_TEST_PFX_PASSWORD, $tmpSign) - $rustSign = @("sign", "--pfx", $pfx, "--password", $env:SIGNTOOL_RS_TEST_PFX_PASSWORD, "--digest", "sha256", $tmpSign) + if (Test-RsEnvPresent "PSIGN_TEST_PFX_PASSWORD") { + $nativeSign = @("sign", "/fd", "SHA256", "/f", $pfx, "/p", $env:PSIGN_TEST_PFX_PASSWORD, $tmpSign) + $rustSign = @("sign", "--pfx", $pfx, "--password", $env:PSIGN_TEST_PFX_PASSWORD, "--digest", "sha256", $tmpSign) } Invoke-Pair "sign_then_verify_pe" $nativeSign $rustSign $rustSignRustSip = $rustSign + @("--rust-sip", "pe") Invoke-Pair "sign_then_verify_pe_rust_sip_digest_gate" $nativeSign $rustSignRustSip Invoke-Pair "verify_signed_pe" @("verify", "/pa", $tmpSign) @("verify", "--policy", "pa", $tmpSign) Remove-Item -LiteralPath $tmpSign -Force -ErrorAction SilentlyContinue - Write-Host "(PE fixture smoke used SIGNTOOL_RS_UNSIGNED_FIXTURE)" + Write-Host "(PE fixture smoke used PSIGN_UNSIGNED_FIXTURE)" } else { - Write-Host "[skip] PE sign smoke: set SIGNTOOL_RS_UNSIGNED_FIXTURE and SIGNTOOL_RS_TEST_PFX" + Write-Host "[skip] PE sign smoke: set PSIGN_UNSIGNED_FIXTURE and PSIGN_TEST_PFX" } -if ((Test-RsEnvPresent "SIGNTOOL_RS_WINMD_UNSIGNED_FIXTURE") -and (Test-RsEnvPresent "SIGNTOOL_RS_TEST_PFX")) { - $w = $env:SIGNTOOL_RS_WINMD_UNSIGNED_FIXTURE +if ((Test-RsEnvPresent "PSIGN_WINMD_UNSIGNED_FIXTURE") -and (Test-RsEnvPresent "PSIGN_TEST_PFX")) { + $w = $env:PSIGN_WINMD_UNSIGNED_FIXTURE if (Test-Path -LiteralPath $w) { - $pfx = $env:SIGNTOOL_RS_TEST_PFX + $pfx = $env:PSIGN_TEST_PFX $tmpW = Join-Path $env:TEMP "psign_sip_smoke.winmd" Copy-Item -LiteralPath $w -Destination $tmpW -Force $nativeSign = @("sign", "/fd", "SHA256", "/f", $pfx, $tmpW) $rustSign = @("sign", "--pfx", $pfx, "--digest", "sha256", $tmpW) - if (Test-RsEnvPresent "SIGNTOOL_RS_TEST_PFX_PASSWORD") { - $nativeSign = @("sign", "/fd", "SHA256", "/f", $pfx, "/p", $env:SIGNTOOL_RS_TEST_PFX_PASSWORD, $tmpW) - $rustSign = @("sign", "--pfx", $pfx, "--password", $env:SIGNTOOL_RS_TEST_PFX_PASSWORD, "--digest", "sha256", $tmpW) + if (Test-RsEnvPresent "PSIGN_TEST_PFX_PASSWORD") { + $nativeSign = @("sign", "/fd", "SHA256", "/f", $pfx, "/p", $env:PSIGN_TEST_PFX_PASSWORD, $tmpW) + $rustSign = @("sign", "--pfx", $pfx, "--password", $env:PSIGN_TEST_PFX_PASSWORD, "--digest", "sha256", $tmpW) } - if (Test-RsEnvPresent "SIGNTOOL_RS_WINMD_TIMESTAMP_URL") { - $nativeSign += @("/tr", $env:SIGNTOOL_RS_WINMD_TIMESTAMP_URL, "/td", "SHA256") - $rustSign += @("--timestamp-url", $env:SIGNTOOL_RS_WINMD_TIMESTAMP_URL, "--timestamp-digest", "sha256") + if (Test-RsEnvPresent "PSIGN_WINMD_TIMESTAMP_URL") { + $nativeSign += @("/tr", $env:PSIGN_WINMD_TIMESTAMP_URL, "/td", "SHA256") + $rustSign += @("--timestamp-url", $env:PSIGN_WINMD_TIMESTAMP_URL, "--timestamp-digest", "sha256") } Invoke-Pair "sign_then_verify_winmd" $nativeSign $rustSign $rustWinmdRustSip = $rustSign + @("--rust-sip", "pe") @@ -119,7 +119,7 @@ if ((Test-RsEnvPresent "SIGNTOOL_RS_WINMD_UNSIGNED_FIXTURE") -and (Test-RsEnvPre Remove-Item -LiteralPath $tmpW -Force -ErrorAction SilentlyContinue } } else { - Write-Host "[skip] WinMD smoke: set SIGNTOOL_RS_WINMD_UNSIGNED_FIXTURE (+ SIGNTOOL_RS_TEST_PFX)" + Write-Host "[skip] WinMD smoke: set PSIGN_WINMD_UNSIGNED_FIXTURE (+ PSIGN_TEST_PFX)" } Write-Host "" diff --git a/src/bin/azure_sign_tool_compat.rs b/src/bin/azure_sign_tool_compat.rs index 99e4d1a..e8080a6 100644 --- a/src/bin/azure_sign_tool_compat.rs +++ b/src/bin/azure_sign_tool_compat.rs @@ -1,4 +1,4 @@ -//! Thin compatibility entry point: sets Azure-style HRESULT batch defaults (`SIGNTOOL_RS_EXIT_CODES`). +//! Thin compatibility entry point: sets Azure-style HRESULT batch defaults (`PSIGN_EXIT_CODES`). #[cfg(not(windows))] fn main() { @@ -10,7 +10,7 @@ fn main() { fn main() { // SAFETY: single-threaded process startup before spawning workers; sets HRESULT-style defaults for scripts. unsafe { - std::env::set_var("SIGNTOOL_RS_EXIT_CODES", "azure"); + std::env::set_var("PSIGN_EXIT_CODES", "azure"); } psign::run_windows_cli(); } diff --git a/src/bin/psign_tool_windows.rs b/src/bin/psign_tool_windows.rs new file mode 100644 index 0000000..6cee95c --- /dev/null +++ b/src/bin/psign_tool_windows.rs @@ -0,0 +1,3 @@ +fn main() { + psign::run_tool_cli(); +} diff --git a/src/cli.rs b/src/cli.rs index 400e610..a1908a7 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -1,9 +1,10 @@ use clap::{Args, Parser, Subcommand, ValueEnum}; +use std::ffi::OsString; use std::path::PathBuf; #[derive(Parser, Debug)] #[command( - name = "psign-tool-windows", + name = "psign-tool", version, about = "Rust reimplementation of signtool.exe (Windows CryptoAPI / WinTrust)" )] @@ -25,11 +26,23 @@ pub struct GlobalOpts { /// Debug diagnostics (native `/debug`). #[arg(long, global = true)] pub debug: bool, + /// Backend mode: auto chooses the native Windows backend on Windows and portable Rust backend elsewhere. + #[arg(long, value_enum, global = true)] + pub mode: Option, +} + +#[derive(Copy, Clone, Debug, Eq, PartialEq, ValueEnum)] +pub enum ToolMode { + Auto, + Windows, + Portable, } #[derive(Subcommand, Debug)] #[allow(clippy::large_enum_variant)] // Subcommands mirror native `signtool` argv shapes; indirection hurts clap ergonomics. pub enum Command { + /// Portable-only diagnostics and helpers (no Win32 APIs). + Portable(PortableArgs), /// Verify embedded Authenticode signature on a file. Verify(VerifyArgs), /// Sign a file using mssign32 (`SignerSignEx3`). @@ -40,7 +53,7 @@ pub enum Command { Catdb(CatdbArgs), /// Remove embedded signature data from a PE file (native `remove`). Remove(RemoveArgs), - /// Inspect Authenticode PKCS#7 layers (nested `1.3.6.1.4.1.311.2.4.1`, timestamp OIDs) as JSON — same portable parser as **`psign-tool-portable inspect-authenticode`**. + /// Inspect Authenticode PKCS#7 layers (nested `1.3.6.1.4.1.311.2.4.1`, timestamp OIDs) as JSON — same portable parser as **`psign-tool portable inspect-authenticode`**. InspectSignature(InspectSignatureArgs), /// Sign Remote Desktop Protocol `.rdp` files (native `rdpsign.exe` semantics). Rdp(RdpArgs), @@ -49,6 +62,14 @@ pub enum Command { ArtifactSigningSubmit(ArtifactSigningSubmitArgs), } +#[derive(Args, Debug)] +#[command(disable_help_flag = true)] +pub struct PortableArgs { + /// Arguments passed to the portable command parser (for example: `pe-digest file.exe`). + #[arg(trailing_var_arg = true, allow_hyphen_values = true)] + pub args: Vec, +} + #[derive(Copy, Clone, Debug, Eq, PartialEq, ValueEnum)] pub enum VerifyPolicy { Default, @@ -56,7 +77,7 @@ pub enum VerifyPolicy { Pg, } -/// Experimental Rust SIP backend selector (`sign --rust-sip …`, `SIGNTOOL_RS_RUST_SIP`). +/// Experimental Rust SIP backend selector (`sign --rust-sip …`, `PSIGN_RUST_SIP`). #[derive(Copy, Clone, Debug, Eq, PartialEq, ValueEnum)] pub enum RustSipBackend { /// PE / PE-like WinMD post-sign Authenticode digest consistency check after OS signing. @@ -80,7 +101,7 @@ pub enum RustSipBackend { /// PKCS#7 `.cat` catalog — CMS digest over encapsulated CTL `eContent` vs PKCS#9 `messageDigest` (`WINTRUST` SIP). #[value(name = "catalog")] Catalog, - /// Ignore `SIGNTOOL_RS_RUST_SIP` for this invocation. + /// Ignore `PSIGN_RUST_SIP` for this invocation. #[value(name = "off")] Off, } @@ -88,7 +109,7 @@ pub enum RustSipBackend { /// Exit-code scheme for batch `sign` (AzureSignTool uses HRESULT-style values when enabled). #[derive(Copy, Clone, Debug, Eq, PartialEq, ValueEnum)] pub enum SignExitCodes { - /// Classic psign-tool-windows semantics (`0` ok, `1` error, `2` warning). + /// Classic psign-tool semantics (`0` ok, `1` error, `2` warning). #[value(name = "signtool")] Signtool, /// AzureSignTool-style HRESULT batch codes (`0`, `0x20000001` partial success, `0xA0000002` all failed). @@ -465,7 +486,7 @@ pub struct SignArgs { /// Enclave warnings do not change exit code (native `/noenclavewarn`) — not implemented. #[arg(long = "noenclavewarn")] pub sign_no_enclave_warn: bool, - /// Experimental Rust SIP behavior (`pe` / `script` / `msi` / `esd` / `msix` / `cab` = post-sign digest consistency check). Override with env `SIGNTOOL_RS_RUST_SIP` when unset; use `off` to ignore the env var. + /// Experimental Rust SIP behavior (`pe` / `script` / `msi` / `esd` / `msix` / `cab` = post-sign digest consistency check). Override with env `PSIGN_RUST_SIP` when unset; use `off` to ignore the env var. #[arg(long = "rust-sip", value_enum)] pub rust_sip: Option, /// Azure Key Vault URL (`AzureSignTool` `--azure-key-vault-url` / `-kvu`). Requires `--features azure-kv-sign`. @@ -503,7 +524,7 @@ pub struct SignArgs { /// Cap concurrent signing threads for multi-file batches (`-mdop`). `1` forces sequential signing. #[arg(long = "max-degree-of-parallelism", visible_alias = "mdop")] pub max_degree_parallelism: Option, - /// Batch exit-code scheme; overrides env when set. Env: `SIGNTOOL_RS_EXIT_CODES=azure|signtool`. + /// Batch exit-code scheme; overrides env when set. Env: `PSIGN_EXIT_CODES=azure|signtool`. #[arg(long = "exit-codes", value_enum)] pub exit_codes: Option, /// File(s) to sign (native trailing ``). diff --git a/src/lib.rs b/src/lib.rs index 88d9df4..e846aa3 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -44,46 +44,247 @@ impl CommandOutput { pub const AZURE_SIGN_EXIT_PARTIAL_SUCCESS: i32 = 0x2000_0001_u32 as i32; pub const AZURE_SIGN_EXIT_ALL_FAILED: i32 = 0xA000_0002_u32 as i32; -#[cfg(windows)] -pub fn run_windows_cli() -> ! { - use crate::cli::{Cli, Command}; - use clap::Parser; +pub const ENV_TOOL_MODE: &str = "PSIGN_TOOL_MODE"; +pub const ENV_RUST_SIP: &str = "PSIGN_RUST_SIP"; +pub const ENV_EXIT_CODES: &str = "PSIGN_EXIT_CODES"; + +pub const LEGACY_ENV_RUST_SIP: &str = "SIGNTOOL_RS_RUST_SIP"; +pub const LEGACY_ENV_EXIT_CODES: &str = "SIGNTOOL_RS_EXIT_CODES"; + +pub fn env_var_with_legacy(name: &str, legacy_name: &str) -> Option { + std::env::var(name) + .ok() + .or_else(|| std::env::var(legacy_name).ok()) +} + +fn parse_tool_mode(value: &str) -> anyhow::Result { + use crate::cli::ToolMode; + let t = value.trim(); + if t.eq_ignore_ascii_case("auto") { + Ok(ToolMode::Auto) + } else if t.eq_ignore_ascii_case("windows") || t.eq_ignore_ascii_case("win32") { + Ok(ToolMode::Windows) + } else if t.eq_ignore_ascii_case("portable") { + Ok(ToolMode::Portable) + } else { + Err(anyhow::anyhow!( + "{ENV_TOOL_MODE} must be one of: auto, windows, portable" + )) + } +} - fn print_output(global: &crate::cli::GlobalOpts, out: &CommandOutput) { - if global.debug { - eprintln!( - "[debug] exit_code={} stdout_len={}", - out.exit_code, - out.stdout.len() - ); +fn resolved_tool_mode(global: &crate::cli::GlobalOpts) -> anyhow::Result { + if let Some(mode) = global.mode { + return Ok(mode); + } + match std::env::var(ENV_TOOL_MODE) { + Ok(value) => parse_tool_mode(&value), + Err(_) => Ok(crate::cli::ToolMode::Auto), + } +} + +fn effective_tool_mode(mode: crate::cli::ToolMode) -> crate::cli::ToolMode { + match mode { + crate::cli::ToolMode::Auto => { + if cfg!(windows) { + crate::cli::ToolMode::Windows + } else { + crate::cli::ToolMode::Portable + } } - if !global.quiet || out.exit_code != 0 { - print!("{}", out.stdout); + explicit => explicit, + } +} + +fn print_output(global: &crate::cli::GlobalOpts, out: &CommandOutput) { + if global.debug { + eprintln!( + "[debug] exit_code={} stdout_len={}", + out.exit_code, + out.stdout.len() + ); + } + if !global.quiet || out.exit_code != 0 { + print!("{}", out.stdout); + } +} + +fn run_portable_args(args: &[std::ffi::OsString]) -> anyhow::Result { + let mut argv = Vec::with_capacity(args.len() + 1); + argv.push(std::ffi::OsString::from("psign-tool")); + if args.is_empty() { + argv.push(std::ffi::OsString::from("--help")); + } else { + argv.extend(args.iter().cloned()); + } + psign_digest_cli::run_from(argv)?; + Ok(CommandOutput::ok(String::new())) +} + +fn portable_command_for_path(path: &std::path::Path) -> anyhow::Result<&'static str> { + let ext = path + .extension() + .and_then(|e| e.to_str()) + .map(str::to_ascii_lowercase) + .unwrap_or_default(); + match ext.as_str() { + "exe" | "dll" | "sys" | "ocx" | "efi" | "winmd" => Ok("verify-pe"), + "cab" => Ok("verify-cab"), + "msi" | "msp" => Ok("verify-msi"), + "wim" | "esd" => Ok("verify-esd"), + "msix" | "appx" | "msixbundle" | "appxbundle" => Ok("verify-msix"), + "cat" => Ok("verify-catalog"), + "ps1" | "psd1" | "psm1" | "ps1xml" | "psc1" | "cdxml" | "mof" | "js" | "vbs" | "wsf" => { + Ok("verify-script") } + _ => Err(anyhow::anyhow!( + "portable verify cannot infer a supported SIP format for {}", + path.display() + )), } +} - fn execute(cli: &Cli) -> anyhow::Result { - match &cli.command { - Command::Verify(args) => crate::win::verify::verify_file(args, &cli.global), - Command::Sign(args) => crate::win::sign::sign_file(args, &cli.global), - Command::Timestamp(args) => crate::win::timestamp::timestamp_file(args, &cli.global), - Command::Catdb(args) => crate::win::catdb::catdb_command(args, &cli.global), - Command::Remove(args) => { - crate::win::remove_signature::remove_command(args, &cli.global) - } - Command::InspectSignature(args) => { - crate::win::inspect_signature::inspect_signature_command(args, &cli.global) - } - Command::Rdp(args) => crate::win::rdp::rdp_command(args, &cli.global), - #[cfg(feature = "artifact-signing-rest")] - Command::ArtifactSigningSubmit(args) => { - crate::win::artifact_signing_rest::artifact_signing_submit_command( - args, - &cli.global, - ) - } +fn portable_verify_unsupported(args: &crate::cli::VerifyArgs) -> bool { + args.policy != crate::cli::VerifyPolicy::Default + || args.policy_guid.is_some() + || args.revocation_check + || args.detached_pkcs7.is_some() + || args.catalog.is_some() + || args.catalog_search.is_some() + || args.catalog_database_guid.is_some() + || args.os_version_check.is_some() + || args.kernel_policy + || args.all_signatures + || args.allow_test_root + || args.warn_if_not_timestamped + || args.signature_index.is_some() + || args.multiple_semantics + || args.verify_pkcs7_file + || args.print_description + || args.verify_page_hashes + || args.chain_root_subject.is_some() + || !args.signer_thumbprint_sha1.is_empty() + || !args.intermediate_ca_sha1.is_empty() + || !args.warn_if_missing_eku.is_empty() + || args.detached_pkcs7_content.is_some() + || args.warn_pca_2010 + || args.no_warn_pca_2010 + || args.verify_sealing_signatures + || args.rust_sip_pe_digest_check + || args.rust_sip_script_digest_check + || args.rust_sip_msi_digest_check + || args.rust_sip_esd_digest_check + || args.rust_sip_msix_digest_check + || args.rust_sip_cab_digest_check + || args.rust_sip_catalog_digest_check + || args.rust_sip_all_digest_checks + || args.biometric_policy + || args.enclave_policy +} + +fn execute_portable_verify(args: &crate::cli::VerifyArgs) -> anyhow::Result { + if portable_verify_unsupported(args) { + return Err(anyhow::anyhow!( + "--mode portable verify currently supports bare file digest-consistency verification; use `psign-tool portable ...` for portable trust/diagnostic commands" + )); + } + for path in &args.files { + let command = portable_command_for_path(path)?; + let argv = [ + std::ffi::OsString::from(command), + path.as_os_str().to_os_string(), + ]; + run_portable_args(&argv)?; + } + Ok(CommandOutput::ok(String::new())) +} + +fn execute_portable_inspect( + args: &crate::cli::InspectSignatureArgs, +) -> anyhow::Result { + let input = match args.input { + crate::cli::InspectSignatureInput::Pe => "pe", + crate::cli::InspectSignatureInput::Pkcs7 => "pkcs7", + }; + let argv = [ + std::ffi::OsString::from("inspect-authenticode"), + args.path.as_os_str().to_os_string(), + std::ffi::OsString::from("--input"), + std::ffi::OsString::from(input), + ]; + run_portable_args(&argv) +} + +#[cfg(windows)] +fn execute_windows(cli: &crate::cli::Cli) -> anyhow::Result { + use crate::cli::Command; + match &cli.command { + Command::Portable(args) => run_portable_args(&args.args), + Command::Verify(args) => crate::win::verify::verify_file(args, &cli.global), + Command::Sign(args) => crate::win::sign::sign_file(args, &cli.global), + Command::Timestamp(args) => crate::win::timestamp::timestamp_file(args, &cli.global), + Command::Catdb(args) => crate::win::catdb::catdb_command(args, &cli.global), + Command::Remove(args) => crate::win::remove_signature::remove_command(args, &cli.global), + Command::InspectSignature(args) => { + crate::win::inspect_signature::inspect_signature_command(args, &cli.global) + } + Command::Rdp(args) => crate::win::rdp::rdp_command(args, &cli.global), + #[cfg(feature = "artifact-signing-rest")] + Command::ArtifactSigningSubmit(args) => { + crate::win::artifact_signing_rest::artifact_signing_submit_command(args, &cli.global) } } +} + +#[cfg(not(windows))] +fn execute_windows(_cli: &crate::cli::Cli) -> anyhow::Result { + Err(anyhow::anyhow!( + "--mode windows requires Microsoft Windows (WinVerifyTrust, SignerSignEx3, registered CryptSIP)" + )) +} + +fn execute_portable(cli: &crate::cli::Cli) -> anyhow::Result { + use crate::cli::Command; + match &cli.command { + Command::Portable(args) => run_portable_args(&args.args), + Command::Verify(args) => execute_portable_verify(args), + Command::InspectSignature(args) => execute_portable_inspect(args), + Command::Sign(_) => Err(anyhow::anyhow!( + "--mode portable sign is not implemented; portable signing helpers are available under `psign-tool portable ...`" + )), + Command::Timestamp(_) => Err(anyhow::anyhow!( + "--mode portable timestamp is not implemented; portable timestamp helpers are available under `psign-tool portable ...`" + )), + Command::Catdb(_) => Err(anyhow::anyhow!( + "--mode portable catdb is unsupported because catalog database operations require Win32" + )), + Command::Remove(_) => Err(anyhow::anyhow!( + "--mode portable remove is unsupported; embedded signature removal currently requires the Windows implementation" + )), + Command::Rdp(_) => Err(anyhow::anyhow!( + "--mode portable rdp is available as `psign-tool portable rdp ...`" + )), + #[cfg(feature = "artifact-signing-rest")] + Command::ArtifactSigningSubmit(_) => Err(anyhow::anyhow!( + "--mode portable artifact-signing-submit is available as `psign-tool portable artifact-signing-submit ...`" + )), + } +} + +fn execute(cli: &crate::cli::Cli) -> anyhow::Result { + if let crate::cli::Command::Portable(args) = &cli.command { + return run_portable_args(&args.args); + } + match effective_tool_mode(resolved_tool_mode(&cli.global)?) { + crate::cli::ToolMode::Windows => execute_windows(cli), + crate::cli::ToolMode::Portable => execute_portable(cli), + crate::cli::ToolMode::Auto => unreachable!("auto mode is resolved before dispatch"), + } +} + +pub fn run_tool_cli() -> ! { + use crate::cli::Cli; + use clap::Parser; let argv: Vec = std::env::args_os().collect(); let Some((executable, tail)) = argv.split_first().map(|(e, t)| (e.clone(), t.to_vec())) else { @@ -123,3 +324,8 @@ pub fn run_windows_cli() -> ! { std::process::exit(batch_exit); } + +#[cfg(windows)] +pub fn run_windows_cli() -> ! { + run_tool_cli(); +} diff --git a/src/main.rs b/src/main.rs index 233c77a..6cee95c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,15 +1,3 @@ -#[cfg(not(windows))] fn main() { - eprintln!( - "psign-tool-windows requires Microsoft Windows (WinVerifyTrust, SignerSignEx3, registered CryptSIP)." - ); - eprintln!( - "Portable CLI: install `psign-tool-portable` (`cargo install --path crates/psign-digest-cli --locked`) or run `cargo test -p psign-sip-digest --lib`." - ); - std::process::exit(1); -} - -#[cfg(windows)] -fn main() { - psign::run_windows_cli(); + psign::run_tool_cli(); } diff --git a/src/native_argv.rs b/src/native_argv.rs index 89ed2b5..c2c14ed 100644 --- a/src/native_argv.rs +++ b/src/native_argv.rs @@ -377,12 +377,11 @@ pub fn normalize_native_signtool_argv(args: Vec) -> Vec { }; let mut globals: Vec = Vec::new(); - for (i, arg) in args.iter().enumerate().skip(1) { - if i == vi { - continue; - } + for arg in args.iter().skip(1).take(vi - 1) { if let Some(g) = try_global_slash(arg) { globals.extend(g); + } else { + globals.push(arg.clone()); } } @@ -394,7 +393,8 @@ pub fn normalize_native_signtool_argv(args: Vec) -> Vec { let mut i = vi + 1; while i < args.len() { let arg = &args[i]; - if try_global_slash(arg).is_some() { + if let Some(g) = try_global_slash(arg) { + out.extend(g); i += 1; continue; } diff --git a/src/response_argv.rs b/src/response_argv.rs index 8097d76..19e372b 100644 --- a/src/response_argv.rs +++ b/src/response_argv.rs @@ -1,6 +1,6 @@ //! Expand native `signtool @responsefile` argument lists (see `signtool-help-root.txt`). //! -//! When the executable is invoked as `psign-tool-windows @path`, the file is read: one argument per +//! When the executable is invoked as `psign-tool @path`, the file is read: one argument per //! line, optional blank line between command blocks (multiple invocations). Otherwise, any //! tail argument starting with a single `@` is treated as a response path and spliced. A //! leading `@@` is not a splice: one `@` is stripped so the argument can start with `@`. @@ -179,7 +179,7 @@ fn prepend_executable(executable: &OsString, tail: Vec) -> Vec /// /// `tail` is `std::env::args_os().skip(1)` (everything after the program path). pub fn expand_invocations(executable: OsString, tail: Vec) -> Result>> { - // Classic: `psign-tool-windows @file` — multi-block response file (blank line between commands). + // Classic: `psign-tool @file` — multi-block response file (blank line between commands). if tail.len() == 1 && let Some(path) = is_response_path_arg(&tail[0]) { @@ -242,7 +242,7 @@ mod tests { let rsp = dir.join("psign_rsp_outer.txt"); fs::write(&rsp, format!("verify\n@{}\nx.exe\n", inner.display())).expect("write rsp"); - let exe = OsString::from("psign-tool-windows"); + let exe = OsString::from("psign-tool"); let inv = expand_invocations( exe.clone(), vec![OsString::from(format!("@{}", rsp.display()))], @@ -254,7 +254,7 @@ mod tests { .iter() .map(|s| s.to_string_lossy().to_string()) .collect::>(), - vec!["psign-tool-windows", "verify", "/pa", "x.exe",] + vec!["psign-tool", "verify", "/pa", "x.exe",] ); let _ = fs::remove_file(&inner); let _ = fs::remove_file(&rsp); @@ -266,7 +266,7 @@ mod tests { let rsp = dir.join("psign_rsp_multiblock.txt"); fs::write(&rsp, "verify\n/pa\na.exe\n\nsign\n/fd\nSHA256\nb.exe\n").expect("write"); - let exe = OsString::from("psign-tool-windows"); + let exe = OsString::from("psign-tool"); let inv = expand_invocations( exe.clone(), vec![OsString::from(format!("@{}", rsp.display()))], @@ -278,14 +278,14 @@ mod tests { .iter() .map(|s| s.to_string_lossy().to_string()) .collect::>(), - vec!["psign-tool-windows", "verify", "/pa", "a.exe",] + vec!["psign-tool", "verify", "/pa", "a.exe",] ); assert_eq!( inv[1] .iter() .map(|s| s.to_string_lossy().to_string()) .collect::>(), - vec!["psign-tool-windows", "sign", "/fd", "SHA256", "b.exe",] + vec!["psign-tool", "sign", "/fd", "SHA256", "b.exe",] ); let _ = fs::remove_file(&rsp); } @@ -337,7 +337,7 @@ mod tests { ) .expect("write rsp"); - let exe = OsString::from("psign-tool-windows"); + let exe = OsString::from("psign-tool"); let inv = expand_invocations( exe.clone(), vec![OsString::from(format!("@{}", rsp.display()))], @@ -351,7 +351,7 @@ mod tests { assert_eq!( argv, vec![ - "psign-tool-windows".to_string(), + "psign-tool".to_string(), "verify".to_string(), "--policy".to_string(), "pa".to_string(), @@ -375,7 +375,7 @@ mod tests { ) .expect("write rsp"); - let exe = OsString::from("psign-tool-windows"); + let exe = OsString::from("psign-tool"); let inv = expand_invocations( exe.clone(), vec![OsString::from(format!("@{}", rsp.display()))], @@ -389,7 +389,7 @@ mod tests { assert_eq!( argv, vec![ - "psign-tool-windows".to_string(), + "psign-tool".to_string(), "verify".to_string(), "/pa".to_string(), "x.exe".to_string(), @@ -410,7 +410,7 @@ mod tests { } fs::write(&rsp, &raw).expect("write utf-16 rsp"); - let exe = OsString::from("psign-tool-windows"); + let exe = OsString::from("psign-tool"); let inv = expand_invocations( exe.clone(), vec![OsString::from(format!("@{}", rsp.display()))], @@ -423,7 +423,7 @@ mod tests { .map(|s| s.to_string_lossy().to_string()) .collect::>(), vec![ - "psign-tool-windows".to_string(), + "psign-tool".to_string(), "verify".to_string(), "/pa".to_string(), "x.exe".to_string(), @@ -443,7 +443,7 @@ mod tests { } fs::write(&rsp, &raw).expect("write utf-16be rsp"); - let exe = OsString::from("psign-tool-windows"); + let exe = OsString::from("psign-tool"); let inv = expand_invocations( exe.clone(), vec![OsString::from(format!("@{}", rsp.display()))], @@ -456,7 +456,7 @@ mod tests { .map(|s| s.to_string_lossy().to_string()) .collect::>(), vec![ - "psign-tool-windows".to_string(), + "psign-tool".to_string(), "verify".to_string(), "/pa".to_string(), "y.exe".to_string(), @@ -474,7 +474,7 @@ mod tests { raw.extend_from_slice(body.as_bytes()); fs::write(&rsp, &raw).expect("write utf-8 bom rsp"); - let exe = OsString::from("psign-tool-windows"); + let exe = OsString::from("psign-tool"); let inv = expand_invocations( exe.clone(), vec![OsString::from(format!("@{}", rsp.display()))], @@ -487,7 +487,7 @@ mod tests { .map(|s| s.to_string_lossy().to_string()) .collect::>(), vec![ - "psign-tool-windows".to_string(), + "psign-tool".to_string(), "verify".to_string(), "/pa".to_string(), "z.exe".to_string(), @@ -498,7 +498,7 @@ mod tests { #[test] fn inline_double_at_strips_one_at_for_literal() { - let exe = OsString::from("psign-tool-windows"); + let exe = OsString::from("psign-tool"); let inv = expand_invocations( exe.clone(), vec![ @@ -517,7 +517,7 @@ mod tests { assert_eq!( argv, vec![ - "psign-tool-windows".to_string(), + "psign-tool".to_string(), "sign".to_string(), "y.exe".to_string(), "--f".to_string(), @@ -528,7 +528,7 @@ mod tests { #[test] fn inline_double_at_only_is_single_at_token() { - let exe = OsString::from("psign-tool-windows"); + let exe = OsString::from("psign-tool"); let inv = expand_invocations( exe.clone(), vec![OsString::from("verify"), OsString::from("@@")], @@ -541,7 +541,7 @@ mod tests { assert_eq!( argv, vec![ - "psign-tool-windows".to_string(), + "psign-tool".to_string(), "verify".to_string(), "@".to_string(), ] diff --git a/src/win/code_sign_format.rs b/src/win/code_sign_format.rs index 7caac79..097a192 100644 --- a/src/win/code_sign_format.rs +++ b/src/win/code_sign_format.rs @@ -6,7 +6,7 @@ //! PowerShell scripts (`.ps1`), PE files, Windows metadata (`.winmd`), MSIX packages, Windows Installer //! packages (`.msi`), WIM/ESD images (`.wim`, `.esd`), etc. use the **existing Windows SIP DLL** at runtime by default. //! -//! **Experimental:** `--rust-sip pe` (and `SIGNTOOL_RS_RUST_SIP=pe`) runs an optional **post-sign** +//! **Experimental:** `--rust-sip pe` (and `PSIGN_RUST_SIP=pe`) runs an optional **post-sign** //! PE Authenticode digest consistency check in Rust after `SignerSignEx3`; it does not replace OS SIP //! registration. See `src/win/sip_rust/` (re-exports `psign-sip-digest`) and `docs/rust-sip-architecture.md`. //! diff --git a/src/win/sign.rs b/src/win/sign.rs index f56911b..5e56c0f 100644 --- a/src/win/sign.rs +++ b/src/win/sign.rs @@ -22,14 +22,14 @@ fn rust_sip_backend(args: &SignArgs) -> Option { Pe | Script | Msi | Esd | Msix | Cab | Catalog => Some(b), }; } - if std::env::var("SIGNTOOL_RS_RUST_SIP") + if crate::env_var_with_legacy(crate::ENV_RUST_SIP, crate::LEGACY_ENV_RUST_SIP) .map(|v| v.eq_ignore_ascii_case("off")) .unwrap_or(false) { return None; } - match std::env::var("SIGNTOOL_RS_RUST_SIP") { - Ok(v) => { + match crate::env_var_with_legacy(crate::ENV_RUST_SIP, crate::LEGACY_ENV_RUST_SIP) { + Some(v) => { let t = v.trim(); if t.eq_ignore_ascii_case("pe") { Some(Pe) @@ -49,7 +49,7 @@ fn rust_sip_backend(args: &SignArgs) -> Option { None } } - Err(_) => None, + None => None, } } @@ -96,11 +96,11 @@ fn ensure_rust_sip_pe_allowed_for_format(path: &Path) -> Result<()> { match crate::win::code_sign_format::detect(path) { PortableExecutable | WindowsMetadata => Ok(()), MsixFamily => Err(anyhow!( - "--rust-sip pe (or SIGNTOOL_RS_RUST_SIP=pe) does not apply to MSIX/AppX packages; \ + "--rust-sip pe (or PSIGN_RUST_SIP=pe) does not apply to MSIX/AppX packages; \ use the OS AppX SIP / native signtool — disable Rust SIP with `--rust-sip off`" )), PowerShellScript | PowerShellModule | PowerShellManifest => Err(anyhow!( - "use `--rust-sip script` (or SIGNTOOL_RS_RUST_SIP=script) for PowerShell-class files, not `--rust-sip pe`" + "use `--rust-sip script` (or PSIGN_RUST_SIP=script) for PowerShell-class files, not `--rust-sip pe`" )), WindowsInstaller | Catalog | Cabinet | WimImage => Err(anyhow!( "--rust-sip pe applies only to PE-based portable executables (.exe, .dll, .winmd, …); \ @@ -177,8 +177,8 @@ fn resolved_sign_exit_codes(args: &SignArgs) -> SignExitCodes { if let Some(x) = args.exit_codes { return x; } - match std::env::var("SIGNTOOL_RS_EXIT_CODES") { - Ok(v) => { + match crate::env_var_with_legacy(crate::ENV_EXIT_CODES, crate::LEGACY_ENV_EXIT_CODES) { + Some(v) => { let t = v.trim(); if t.eq_ignore_ascii_case("azure") || t.eq_ignore_ascii_case("azuresigntool") { SignExitCodes::Azuresigntool @@ -186,7 +186,7 @@ fn resolved_sign_exit_codes(args: &SignArgs) -> SignExitCodes { SignExitCodes::Signtool } } - Err(_) => SignExitCodes::Signtool, + None => SignExitCodes::Signtool, } } diff --git a/src/win/sign_core.rs b/src/win/sign_core.rs index e1a6ee4..1fa7137 100644 --- a/src/win/sign_core.rs +++ b/src/win/sign_core.rs @@ -290,7 +290,7 @@ fn load_decoupled_digest_info( .map_err(|e| { anyhow!( "LoadLibraryW('{}') failed: {e}. \ -If using Azure Artifact Signing: install .NET 8, deploy the full NuGet `bin\\{arch}` directory (all dependent DLLs), and use the {arch} `Azure.CodeSigning.Dlib.dll` with a {arch} psign-tool-windows build.", +If using Azure Artifact Signing: install .NET 8, deploy the full NuGet `bin\\{arch}` directory (all dependent DLLs), and use the {arch} `Azure.CodeSigning.Dlib.dll` with a {arch} psign-tool build.", dlib.display() ) })?; diff --git a/tests/cli_verify_filters.rs b/tests/cli_verify_filters.rs index 878d475..cc6cfe5 100644 --- a/tests/cli_verify_filters.rs +++ b/tests/cli_verify_filters.rs @@ -13,7 +13,7 @@ use std::path::{Path, PathBuf}; #[test] fn verify_os_version_check_parses() { let c = Cli::try_parse_from([ - "psign-tool-windows", + "psign-tool", "verify", "--policy", "pa", @@ -32,7 +32,7 @@ fn verify_os_version_check_parses() { fn verify_os_version_check_requires_catalog_at_runtime() { let tmp = std::env::temp_dir().join("psign_osver_guard_probe.bin"); fs::write(&tmp, b"x").unwrap(); - Command::cargo_bin("psign-tool-windows") + Command::cargo_bin("psign-tool") .expect("binary") .args([ "verify", @@ -51,7 +51,7 @@ fn verify_os_version_check_requires_catalog_at_runtime() { #[test] fn verify_repeatable_thumbprints_and_quiet_short_parse() { let c = Cli::try_parse_from([ - "psign-tool-windows", + "psign-tool", "-q", "verify", "--signer-thumbprint-sha1", @@ -80,7 +80,7 @@ fn verify_repeatable_thumbprints_and_quiet_short_parse() { #[test] fn verify_accepts_multiple_trailing_files() { let c = Cli::try_parse_from([ - "psign-tool-windows", + "psign-tool", "verify", "--policy", "pa", @@ -99,7 +99,7 @@ fn verify_accepts_multiple_trailing_files() { #[test] fn sign_accepts_multiple_trailing_files() { let c = Cli::try_parse_from([ - "psign-tool-windows", + "psign-tool", "sign", "--f", "a.pfx", @@ -120,7 +120,7 @@ fn sign_accepts_multiple_trailing_files() { #[test] fn rdp_accepts_sha256_and_multiple_trailing_files() { let c = Cli::try_parse_from([ - "psign-tool-windows", + "psign-tool", "rdp", "--sha256", "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", @@ -143,12 +143,12 @@ fn rdp_accepts_sha256_and_multiple_trailing_files() { #[test] fn rdp_requires_one_thumbprint_algorithm() { assert!( - Cli::try_parse_from(["psign-tool-windows", "rdp", "file.rdp"]).is_err(), + Cli::try_parse_from(["psign-tool", "rdp", "file.rdp"]).is_err(), "missing thumbprint should fail" ); assert!( Cli::try_parse_from([ - "psign-tool-windows", + "psign-tool", "rdp", "--sha1", "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", @@ -187,7 +187,7 @@ fn rdp_shared_fixtures_decode_in_windows_crate() { #[test] fn timestamp_accepts_multiple_trailing_files() { let c = Cli::try_parse_from([ - "psign-tool-windows", + "psign-tool", "timestamp", "--tr", "http://ts.example/rfc3161", @@ -215,8 +215,7 @@ fn rdp_fixture(name: &str) -> PathBuf { #[test] fn remove_accepts_multiple_trailing_files() { - let c = Cli::try_parse_from(["psign-tool-windows", "remove", "--s", "x.exe", "y.exe"]) - .expect("parse"); + let c = Cli::try_parse_from(["psign-tool", "remove", "--s", "x.exe", "y.exe"]).expect("parse"); let SubCommand::Remove(r) = c.command else { panic!("expected remove"); }; @@ -226,7 +225,7 @@ fn remove_accepts_multiple_trailing_files() { #[test] fn verify_detached_rejects_multiple_content_files() { - Command::cargo_bin("psign-tool-windows") + Command::cargo_bin("psign-tool") .expect("binary available") .args(["verify", "--detached-pkcs7", "sig.p7s", "a.exe", "b.exe"]) .assert() @@ -238,7 +237,7 @@ fn verify_detached_rejects_multiple_content_files() { #[test] fn verify_detached_content_without_detached_errors_at_runtime() { - Command::cargo_bin("psign-tool-windows") + Command::cargo_bin("psign-tool") .expect("binary available") .args(["verify", "--detached-pkcs7-content", "content.bin", "x.exe"]) .assert() @@ -256,7 +255,7 @@ fn verify_wrong_signer_thumbprint_fails_on_signed_pe() { if !fixture.exists() { return; } - Command::cargo_bin("psign-tool-windows") + Command::cargo_bin("psign-tool") .expect("binary available") .args([ "verify", @@ -273,7 +272,7 @@ fn verify_wrong_signer_thumbprint_fails_on_signed_pe() { #[test] fn verify_pca_warn_flags_conflict_at_runtime() { - Command::cargo_bin("psign-tool-windows") + Command::cargo_bin("psign-tool") .expect("binary available") .args(["verify", "--warn-pca-2010", "--no-warn-pca-2010", "x.exe"]) .assert() @@ -283,7 +282,7 @@ fn verify_pca_warn_flags_conflict_at_runtime() { #[test] fn sign_ph_and_nph_mutually_exclusive() { - Command::cargo_bin("psign-tool-windows") + Command::cargo_bin("psign-tool") .expect("binary available") .args(["sign", "--page-hashes", "--no-page-hashes", "nope.exe"]) .assert() @@ -294,7 +293,7 @@ fn sign_ph_and_nph_mutually_exclusive() { #[test] fn verify_detached_p7s_alias_parses() { let c = Cli::try_parse_from([ - "psign-tool-windows", + "psign-tool", "verify", "content.bin", "--p7s", @@ -316,15 +315,8 @@ fn verify_detached_p7s_alias_parses() { #[test] fn verify_vr_alias_sets_revocation_check() { - let c = Cli::try_parse_from([ - "psign-tool-windows", - "verify", - "--vr", - "--policy", - "pa", - "x.exe", - ]) - .expect("parse"); + let c = Cli::try_parse_from(["psign-tool", "verify", "--vr", "--policy", "pa", "x.exe"]) + .expect("parse"); let SubCommand::Verify(v) = c.command else { panic!("expected verify"); }; @@ -334,7 +326,7 @@ fn verify_vr_alias_sets_revocation_check() { #[test] fn verify_testroot_alias_sets_allow_test_root() { let c = Cli::try_parse_from([ - "psign-tool-windows", + "psign-tool", "verify", "--testroot", "--policy", @@ -350,15 +342,8 @@ fn verify_testroot_alias_sets_allow_test_root() { #[test] fn verify_sl_sets_flag_and_runs_embedded_path() { - let c = Cli::try_parse_from([ - "psign-tool-windows", - "verify", - "--sl", - "--policy", - "pa", - "x.exe", - ]) - .expect("parse"); + let c = Cli::try_parse_from(["psign-tool", "verify", "--sl", "--policy", "pa", "x.exe"]) + .expect("parse"); let SubCommand::Verify(v) = c.command else { panic!("expected verify"); }; @@ -368,7 +353,7 @@ fn verify_sl_sets_flag_and_runs_embedded_path() { #[test] #[cfg(windows)] fn verify_sl_rejects_detached_pkcs7() { - let mut cmd = Command::cargo_bin("psign-tool-windows").unwrap(); + let mut cmd = Command::cargo_bin("psign-tool").unwrap(); cmd.args([ "verify", "--policy", @@ -393,7 +378,7 @@ fn at_response_file_single_invocation() { ) .expect("write rsp"); let at = format!("@{}", rsp.display()); - Command::cargo_bin("psign-tool-windows") + Command::cargo_bin("psign-tool") .expect("binary available") .arg(&at) .assert() @@ -403,7 +388,7 @@ fn at_response_file_single_invocation() { #[test] fn timestamp_force_not_implemented() { - Command::cargo_bin("psign-tool-windows") + Command::cargo_bin("psign-tool") .expect("binary available") .args([ "timestamp", @@ -419,7 +404,7 @@ fn timestamp_force_not_implemented() { #[test] fn timestamp_nosealwarn_not_implemented() { - Command::cargo_bin("psign-tool-windows") + Command::cargo_bin("psign-tool") .expect("binary available") .args([ "timestamp", @@ -435,15 +420,8 @@ fn timestamp_nosealwarn_not_implemented() { #[test] fn verify_tw_alias_equivalent_to_long_flag() { - let c = Cli::try_parse_from([ - "psign-tool-windows", - "verify", - "--tw", - "--policy", - "pa", - "x.exe", - ]) - .expect("parse"); + let c = Cli::try_parse_from(["psign-tool", "verify", "--tw", "--policy", "pa", "x.exe"]) + .expect("parse"); let SubCommand::Verify(v) = c.command else { panic!("expected verify"); }; @@ -453,7 +431,7 @@ fn verify_tw_alias_equivalent_to_long_flag() { #[test] fn sign_seal_tseal_url_parses() { let c = Cli::try_parse_from([ - "psign-tool-windows", + "psign-tool", "sign", "--f", "c.pfx", @@ -480,7 +458,7 @@ fn sign_seal_tseal_url_parses() { fn sign_tr_and_tseal_conflict() { assert!( Cli::try_parse_from([ - "psign-tool-windows", + "psign-tool", "sign", "--f", "a.pfx", @@ -499,7 +477,7 @@ fn sign_tr_and_tseal_conflict() { #[test] fn sign_fd_and_tr_aliases_parse() { let c = Cli::try_parse_from([ - "psign-tool-windows", + "psign-tool", "sign", "--f", "cert.pfx", @@ -526,7 +504,7 @@ fn sign_fd_and_tr_aliases_parse() { #[test] fn sign_auth_pairs_parse() { let c = Cli::try_parse_from([ - "psign-tool-windows", + "psign-tool", "sign", "--f", "a.pfx", @@ -550,7 +528,7 @@ fn sign_auth_pairs_parse() { #[test] fn sign_certificate_template_alias_c_parses() { let c = Cli::try_parse_from([ - "psign-tool-windows", + "psign-tool", "sign", "--f", "a.pfx", @@ -569,7 +547,7 @@ fn sign_certificate_template_alias_c_parses() { #[test] fn sign_seal_not_implemented_before_crypto() { - Command::cargo_bin("psign-tool-windows") + Command::cargo_bin("psign-tool") .expect("binary available") .args([ "sign", @@ -587,7 +565,7 @@ fn sign_seal_not_implemented_before_crypto() { #[test] fn sign_certificate_template_not_implemented() { - Command::cargo_bin("psign-tool-windows") + Command::cargo_bin("psign-tool") .expect("binary available") .args([ "sign", @@ -607,7 +585,7 @@ fn sign_certificate_template_not_implemented() { #[test] fn timestamp_native_style_aliases_parse() { let c = Cli::try_parse_from([ - "psign-tool-windows", + "psign-tool", "timestamp", "--tr", "http://ts.example/rfc3161", @@ -629,7 +607,7 @@ fn timestamp_native_style_aliases_parse() { #[test] fn timestamp_tseal_url_parses() { let c = Cli::try_parse_from([ - "psign-tool-windows", + "psign-tool", "timestamp", "--tseal", "http://ts.example/seal", @@ -652,7 +630,7 @@ fn timestamp_tseal_url_parses() { fn timestamp_tr_and_tseal_conflict() { assert!( Cli::try_parse_from([ - "psign-tool-windows", + "psign-tool", "timestamp", "--tr", "http://a", @@ -669,7 +647,7 @@ fn timestamp_tr_and_tseal_conflict() { #[test] #[cfg(windows)] fn remove_strip_chain_missing_file_is_not_stub_error() { - Command::cargo_bin("psign-tool-windows") + Command::cargo_bin("psign-tool") .expect("binary available") .args(["remove", "--c", "psign_remove_c_missing_xyz.exe"]) .assert() @@ -679,7 +657,7 @@ fn remove_strip_chain_missing_file_is_not_stub_error() { #[test] fn remove_requires_one_mode() { - Command::cargo_bin("psign-tool-windows") + Command::cargo_bin("psign-tool") .expect("binary available") .args(["remove", "nope.exe"]) .assert() @@ -691,7 +669,7 @@ fn remove_requires_one_mode() { #[cfg(windows)] fn windows_slash_argv_normalizes_to_clap_verify() { let raw = vec![ - OsString::from("psign-tool-windows"), + OsString::from("psign-tool"), OsString::from("verify"), OsString::from("/pa"), OsString::from("/q"), @@ -708,7 +686,7 @@ fn windows_slash_argv_normalizes_to_clap_verify() { #[test] fn verify_page_hashes_requires_verbose() { - let mut cmd = Command::cargo_bin("psign-tool-windows").unwrap(); + let mut cmd = Command::cargo_bin("psign-tool").unwrap(); cmd.args([ "verify", "--policy", @@ -723,7 +701,7 @@ fn verify_page_hashes_requires_verbose() { #[test] fn verify_print_description_requires_verbose() { - let mut cmd = Command::cargo_bin("psign-tool-windows").unwrap(); + let mut cmd = Command::cargo_bin("psign-tool").unwrap(); cmd.args([ "verify", "--policy", @@ -741,7 +719,7 @@ fn verify_print_description_requires_verbose() { fn remove_strip_signature_rejects_powershell_script() { let ps1 = std::env::temp_dir().join(format!("psign_remove_cli_{}.ps1", std::process::id())); std::fs::write(&ps1, "# parity-remove-test\n").expect("write ps1"); - let mut cmd = Command::cargo_bin("psign-tool-windows").unwrap(); + let mut cmd = Command::cargo_bin("psign-tool").unwrap(); cmd.arg("remove").arg("--strip-signature").arg(&ps1); cmd.assert() .failure() @@ -754,7 +732,7 @@ fn remove_strip_signature_rejects_powershell_script() { fn remove_strip_signature_rejects_js_script() { let js = std::env::temp_dir().join(format!("psign_remove_cli_{}.js", std::process::id())); std::fs::write(&js, "// parity-remove-test\n").expect("write js"); - let mut cmd = Command::cargo_bin("psign-tool-windows").unwrap(); + let mut cmd = Command::cargo_bin("psign-tool").unwrap(); cmd.arg("remove").arg("--strip-signature").arg(&js); cmd.assert() .failure() @@ -767,7 +745,7 @@ fn remove_strip_signature_rejects_js_script() { fn remove_strip_signature_rejects_vbs_script() { let vbs = std::env::temp_dir().join(format!("psign_remove_cli_{}.vbs", std::process::id())); std::fs::write(&vbs, "' parity-remove-test\n").expect("write vbs"); - let mut cmd = Command::cargo_bin("psign-tool-windows").unwrap(); + let mut cmd = Command::cargo_bin("psign-tool").unwrap(); cmd.arg("remove").arg("--strip-signature").arg(&vbs); cmd.assert() .failure() @@ -780,7 +758,7 @@ fn remove_strip_signature_rejects_vbs_script() { fn remove_strip_signature_rejects_msix_package() { let msix = std::env::temp_dir().join(format!("psign_remove_cli_{}.msix", std::process::id())); std::fs::write(&msix, b"not-a-real-msix").expect("write msix"); - let mut cmd = Command::cargo_bin("psign-tool-windows").unwrap(); + let mut cmd = Command::cargo_bin("psign-tool").unwrap(); cmd.arg("remove").arg("--strip-signature").arg(&msix); cmd.assert() .failure() @@ -796,7 +774,7 @@ fn remove_strip_signature_rejects_unknown_extension() { std::process::id() )); std::fs::write(&weird, b"x").expect("write junk"); - let mut cmd = Command::cargo_bin("psign-tool-windows").unwrap(); + let mut cmd = Command::cargo_bin("psign-tool").unwrap(); cmd.arg("remove").arg("--strip-signature").arg(&weird); cmd.assert() .failure() @@ -809,7 +787,7 @@ fn remove_strip_signature_rejects_unknown_extension() { fn remove_strip_signature_rejects_windows_installer_msi() { let msi = std::env::temp_dir().join(format!("psign_remove_cli_{}.msi", std::process::id())); std::fs::write(&msi, b"not-a-real-msi").expect("write msi"); - let mut cmd = Command::cargo_bin("psign-tool-windows").unwrap(); + let mut cmd = Command::cargo_bin("psign-tool").unwrap(); cmd.arg("remove").arg("--strip-signature").arg(&msi); cmd.assert() .failure() @@ -822,7 +800,7 @@ fn remove_strip_signature_rejects_windows_installer_msi() { fn remove_strip_signature_rejects_wim_image() { let wim = std::env::temp_dir().join(format!("psign_remove_cli_{}.wim", std::process::id())); std::fs::write(&wim, b"not-a-real-wim").expect("write wim"); - let mut cmd = Command::cargo_bin("psign-tool-windows").unwrap(); + let mut cmd = Command::cargo_bin("psign-tool").unwrap(); cmd.arg("remove").arg("--strip-signature").arg(&wim); cmd.assert() .failure() @@ -839,7 +817,7 @@ fn remove_strip_signature_rejects_wsf_script() { r#""#, ) .expect("write wsf"); - let mut cmd = Command::cargo_bin("psign-tool-windows").unwrap(); + let mut cmd = Command::cargo_bin("psign-tool").unwrap(); cmd.arg("remove").arg("--strip-signature").arg(&wsf); cmd.assert() .failure() @@ -851,7 +829,7 @@ fn remove_strip_signature_rejects_wsf_script() { #[cfg(windows)] fn windows_slash_argv_normalizes_sign_sa_two_values() { let raw = vec![ - OsString::from("psign-tool-windows"), + OsString::from("psign-tool"), OsString::from("sign"), OsString::from("/f"), OsString::from("a.pfx"), @@ -876,7 +854,7 @@ fn windows_slash_argv_normalizes_sign_sa_two_values() { #[test] fn sign_rust_sip_script_parses() { let c = Cli::try_parse_from([ - "psign-tool-windows", + "psign-tool", "sign", "--pfx", "a.pfx", @@ -896,7 +874,7 @@ fn sign_rust_sip_script_parses() { #[test] fn sign_rust_sip_pe_parses() { let c = Cli::try_parse_from([ - "psign-tool-windows", + "psign-tool", "sign", "--pfx", "a.pfx", @@ -916,7 +894,7 @@ fn sign_rust_sip_pe_parses() { #[test] fn sign_rust_sip_msi_parses() { let c = Cli::try_parse_from([ - "psign-tool-windows", + "psign-tool", "sign", "--pfx", "a.pfx", @@ -936,7 +914,7 @@ fn sign_rust_sip_msi_parses() { #[test] fn sign_rust_sip_msix_parses() { let c = Cli::try_parse_from([ - "psign-tool-windows", + "psign-tool", "sign", "--pfx", "a.pfx", @@ -956,7 +934,7 @@ fn sign_rust_sip_msix_parses() { #[test] fn sign_rust_sip_esd_parses() { let c = Cli::try_parse_from([ - "psign-tool-windows", + "psign-tool", "sign", "--pfx", "a.pfx", @@ -976,7 +954,7 @@ fn sign_rust_sip_esd_parses() { #[test] fn sign_rust_sip_cab_parses() { let c = Cli::try_parse_from([ - "psign-tool-windows", + "psign-tool", "sign", "--pfx", "a.pfx", @@ -996,7 +974,7 @@ fn sign_rust_sip_cab_parses() { #[test] fn sign_rust_sip_catalog_parses() { let c = Cli::try_parse_from([ - "psign-tool-windows", + "psign-tool", "sign", "--pfx", "a.pfx", @@ -1016,7 +994,7 @@ fn sign_rust_sip_catalog_parses() { #[test] fn verify_rust_sip_script_digest_check_parses() { let c = Cli::try_parse_from([ - "psign-tool-windows", + "psign-tool", "verify", "--policy", "pa", @@ -1033,7 +1011,7 @@ fn verify_rust_sip_script_digest_check_parses() { #[test] fn verify_rust_sip_pe_digest_check_parses() { let c = Cli::try_parse_from([ - "psign-tool-windows", + "psign-tool", "verify", "--policy", "pa", @@ -1050,7 +1028,7 @@ fn verify_rust_sip_pe_digest_check_parses() { #[test] fn verify_rust_sip_msi_digest_check_parses() { let c = Cli::try_parse_from([ - "psign-tool-windows", + "psign-tool", "verify", "--policy", "pa", @@ -1067,7 +1045,7 @@ fn verify_rust_sip_msi_digest_check_parses() { #[test] fn verify_rust_sip_msix_digest_check_parses() { let c = Cli::try_parse_from([ - "psign-tool-windows", + "psign-tool", "verify", "--policy", "pa", @@ -1084,7 +1062,7 @@ fn verify_rust_sip_msix_digest_check_parses() { #[test] fn verify_rust_sip_esd_digest_check_parses() { let c = Cli::try_parse_from([ - "psign-tool-windows", + "psign-tool", "verify", "--policy", "pa", @@ -1101,7 +1079,7 @@ fn verify_rust_sip_esd_digest_check_parses() { #[test] fn verify_rust_sip_cab_digest_check_parses() { let c = Cli::try_parse_from([ - "psign-tool-windows", + "psign-tool", "verify", "--policy", "pa", @@ -1118,7 +1096,7 @@ fn verify_rust_sip_cab_digest_check_parses() { #[test] fn verify_rust_sip_catalog_digest_check_parses() { let c = Cli::try_parse_from([ - "psign-tool-windows", + "psign-tool", "verify", "--policy", "pa", @@ -1135,7 +1113,7 @@ fn verify_rust_sip_catalog_digest_check_parses() { #[test] fn verify_rust_sip_all_digest_checks_parses() { let c = Cli::try_parse_from([ - "psign-tool-windows", + "psign-tool", "verify", "--policy", "pa", diff --git a/tests/corpus_sign_verify.rs b/tests/corpus_sign_verify.rs index 89e870d..fc5859d 100644 --- a/tests/corpus_sign_verify.rs +++ b/tests/corpus_sign_verify.rs @@ -148,7 +148,7 @@ fn unsigned_corpus_freshly_signed_with_native_signtool_verifies_with_psign() { #[test] fn unsigned_corpus_freshly_signed_with_psign_verifies_with_psign() { - let thumbprint = std::env::var("SIGNTOOL_RS_TEST_CERT_SHA1") + let thumbprint = std::env::var("PSIGN_TEST_CERT_SHA1") .ok() .filter(|value| !value.trim().is_empty()) .unwrap_or_else(|| TEST_CERT_SHA1.to_owned()); @@ -244,30 +244,13 @@ fn verify_detached_with_portable(repo: &Path, content: &Path, p7: &Path, label: } fn psign() -> Command { - Command::cargo_bin("psign-tool-windows").expect("psign-tool-windows binary") + Command::cargo_bin("psign-tool").expect("psign-tool binary") } fn portable() -> Command { - let exe = portable_exe(); - if !exe.is_file() { - let mut build = Command::new(std::env::var_os("CARGO").unwrap_or_else(|| "cargo".into())); - build.current_dir(repo_root()).args([ - "build", - "-p", - "psign-digest-cli", - "--bin", - "psign-tool-portable", - "--locked", - ]); - if std::env::var("PROFILE").ok().as_deref() == Some("release") { - build.arg("--release"); - } - let status = build - .status() - .unwrap_or_else(|e| panic!("build psign-tool-portable: {e}")); - assert!(status.success(), "build psign-tool-portable failed"); - } - Command::new(exe) + let mut cmd = psign(); + cmd.arg("portable"); + cmd } fn portable_args_for_entry( @@ -356,17 +339,6 @@ fn anchor_dir(repo_root: &Path) -> PathBuf { repo_root.join("tests\\fixtures\\devolutions-authenticode") } -fn portable_exe() -> PathBuf { - if let Some(path) = std::env::var_os("CARGO_BIN_EXE_psign-tool-portable") { - return PathBuf::from(path); - } - let profile = std::env::var("PROFILE").unwrap_or_else(|_| "debug".to_owned()); - let target_dir = std::env::var_os("CARGO_TARGET_DIR") - .map(PathBuf::from) - .unwrap_or_else(|| repo_root().join("target")); - target_dir.join(profile).join("psign-tool-portable.exe") -} - fn native_signtool_optional_path() -> Option { std::env::var_os("SIGNTOOL_EXE") .map(PathBuf::from) diff --git a/tests/cross_cli_windows.rs b/tests/cross_cli_windows.rs index 9883047..0f9f3c7 100644 --- a/tests/cross_cli_windows.rs +++ b/tests/cross_cli_windows.rs @@ -1,7 +1,7 @@ #![cfg(windows)] -//! Cross-CLI parity between **`psign-tool-portable verify-pe`** and the Rust PE Authenticode digest check -//! wired behind **`psign-tool-windows verify --rust-sip-pe-digest-check`**. +//! Cross-CLI parity between **`psign-tool portable verify-pe`** and the Rust PE Authenticode digest check +//! wired behind **`psign-tool verify --rust-sip-pe-digest-check`**. //! //! On stock Windows trust stores the upstream **`tiny*.signed.efi`** fixtures do **not** satisfy //! WinVerifyTrust, so the Windows CLI exits before it can run the Rust SIP digest pass. These tests @@ -11,12 +11,10 @@ use assert_cmd::Command; use std::path::PathBuf; -fn portable_exe() -> PathBuf { - let profile = std::env::var("PROFILE").unwrap_or_else(|_| "debug".into()); - PathBuf::from(env!("CARGO_MANIFEST_DIR")) - .join("target") - .join(profile) - .join("psign-tool-portable.exe") +fn portable_cmd() -> Command { + let mut cmd = Command::cargo_bin("psign-tool").expect("psign-tool binary"); + cmd.arg("portable"); + cmd } fn tiny32_fixture() -> PathBuf { @@ -31,17 +29,10 @@ fn tiny64_fixture() -> PathBuf { #[test] fn portable_verify_pe_agrees_with_windows_rust_sip_pe_digest_routine_tiny32() { - let portable = portable_exe(); - assert!( - portable.is_file(), - "psign-tool-portable missing at {} — run `cargo build --workspace` or `cargo build -p psign-digest-cli --bin psign-tool-portable` first", - portable.display() - ); - let fixture = tiny32_fixture(); assert!(fixture.is_file(), "fixture missing: {}", fixture.display()); - let mut digest_cmd = Command::new(&portable); + let mut digest_cmd = portable_cmd(); digest_cmd.arg("verify-pe").arg(&fixture); digest_cmd.assert().success(); @@ -53,17 +44,10 @@ fn portable_verify_pe_agrees_with_windows_rust_sip_pe_digest_routine_tiny32() { #[test] fn portable_verify_pe_agrees_with_windows_rust_sip_pe_digest_routine_tiny64() { - let portable = portable_exe(); - assert!( - portable.is_file(), - "psign-tool-portable missing at {} — run `cargo build --workspace` or `cargo build -p psign-digest-cli --bin psign-tool-portable` first", - portable.display() - ); - let fixture = tiny64_fixture(); assert!(fixture.is_file(), "fixture missing: {}", fixture.display()); - let mut digest_cmd = Command::new(&portable); + let mut digest_cmd = portable_cmd(); digest_cmd.arg("verify-pe").arg(&fixture); digest_cmd.assert().success(); diff --git a/tests/fixtures/README.md b/tests/fixtures/README.md index b238ddc..fe9ce4f 100644 --- a/tests/fixtures/README.md +++ b/tests/fixtures/README.md @@ -54,25 +54,25 @@ Suggested corpus: MSIX parity fixtures: - `unsigned.msix` (an unsigned package copied for native/rust sign comparisons) - optional decoupled assets referenced by env: - - `SIGNTOOL_RS_MSIX_DLIB` - - `SIGNTOOL_RS_MSIX_DMDF` + - `PSIGN_MSIX_DLIB` + - `PSIGN_MSIX_DMDF` Devolutions test cert quick setup (trusted local testing only): -- `SIGNTOOL_RS_MSIX_TEST_PFX` -> path to `authenticode-test-cert.pfx` -- `SIGNTOOL_RS_MSIX_TEST_PFX_PASSWORD` -> usually `CodeSign123!` -- `SIGNTOOL_RS_MSIX_TEST_CERT_SHA1` -> alternative when cert is already imported in `CurrentUser\My` -- `SIGNTOOL_RS_MSIX_TIMESTAMP_URL` -> RFC3161 timestamp URL for parity runs +- `PSIGN_MSIX_TEST_PFX` -> path to `authenticode-test-cert.pfx` +- `PSIGN_MSIX_TEST_PFX_PASSWORD` -> usually `CodeSign123!` +- `PSIGN_MSIX_TEST_CERT_SHA1` -> alternative when cert is already imported in `CurrentUser\My` +- `PSIGN_MSIX_TIMESTAMP_URL` -> RFC3161 timestamp URL for parity runs MSIX minimal pack layout (CI-generated unsigned package): -- `msix-minimal/AppxManifest.xml` — Identity publisher `CN=Test Code Signing Certificate` (matches Devolutions test signing cert). CI copies `target/debug/psign-tool-windows.exe` as `noop.exe` and adds `Assets/StoreLogo.png` before `MakeAppx pack`. +- `msix-minimal/AppxManifest.xml` — Identity publisher `CN=Test Code Signing Certificate` (matches Devolutions test signing cert). CI copies `target/debug/psign-tool.exe` as `noop.exe` and adds `Assets/StoreLogo.png` before `MakeAppx pack`. Optional MSI parity (`scripts/run-parity-diff.ps1`): -- `SIGNTOOL_RS_MSI_UNSIGNED_FIXTURE` -> path to an unsigned `.msi` (not bundled in-repo) -- Reuses `SIGNTOOL_RS_TEST_PFX` / `_PASSWORD` with PE parity -- Optional `SIGNTOOL_RS_MSI_TIMESTAMP_URL` for sign-time RFC3161 (native `/tr` `/td SHA256`) +- `PSIGN_MSI_UNSIGNED_FIXTURE` -> path to an unsigned `.msi` (not bundled in-repo) +- Reuses `PSIGN_TEST_PFX` / `_PASSWORD` with PE parity +- Optional `PSIGN_MSI_TIMESTAMP_URL` for sign-time RFC3161 (native `/tr` `/td SHA256`) -## Linux Authenticode trust (`psign-tool-portable trust-verify-pe`) +## Linux Authenticode trust (`psign-tool portable trust-verify-pe`) Digest-only PE checks use **`tests/fixtures/pe-authenticode-upstream/tiny32.signed.efi`** and **`tiny64.signed.efi`** (upstream **`authenticode-rs`** corpora; see that subtree’s license/README). **`trust-verify-pe`** needs **configured roots**. CI extracts the embedded terminal root into a temp **`--anchor-dir`** and passes **`--as-of 2023-07-01`** because the test signing cert expires in **2023** (wall-clock verification would fail). diff --git a/tests/fixtures/catalog-authenticode-upstream/README.md b/tests/fixtures/catalog-authenticode-upstream/README.md index 9aa721f..4a3b151 100644 --- a/tests/fixtures/catalog-authenticode-upstream/README.md +++ b/tests/fixtures/catalog-authenticode-upstream/README.md @@ -7,8 +7,8 @@ Regenerate: ```powershell -cargo run -p psign-digest-cli --bin psign-tool-portable -- ` - extract-pe-pkcs7 tests/fixtures/pe-authenticode-upstream/tiny32.signed.efi ` +cargo run -p psign --bin psign-tool -- ` + portable extract-pe-pkcs7 tests/fixtures/pe-authenticode-upstream/tiny32.signed.efi ` --output tests/fixtures/catalog-authenticode-upstream/tiny32-content.cat ``` diff --git a/tests/fixtures/corpus.json b/tests/fixtures/corpus.json index 3f5d8b4..8c3ca65 100644 --- a/tests/fixtures/corpus.json +++ b/tests/fixtures/corpus.json @@ -52,14 +52,14 @@ { "id": "winmd_sign_sha256_pfx", "kind": "sign", - "description": "Sign unsigned .winmd with PFX + SHA256 (Windows metadata / PE-based SIP); optional RFC3161 via SIGNTOOL_RS_WINMD_TIMESTAMP_URL", + "description": "Sign unsigned .winmd with PFX + SHA256 (Windows metadata / PE-based SIP); optional RFC3161 via PSIGN_WINMD_TIMESTAMP_URL", "fixture": "tests/fixtures/private/unsigned.winmd", "expected": "pending_fixture" }, { "id": "msi_sign_sha256_pfx", "kind": "sign", - "description": "Sign unsigned MSI with PFX + SHA256 (Windows Installer SIP); optional RFC3161 via SIGNTOOL_RS_MSI_TIMESTAMP_URL in parity script", + "description": "Sign unsigned MSI with PFX + SHA256 (Windows Installer SIP); optional RFC3161 via PSIGN_MSI_TIMESTAMP_URL in parity script", "fixture": "tests/fixtures/private/unsigned.msi", "expected": "pending_fixture" }, @@ -150,7 +150,7 @@ { "id": "rust_sip_pe_sign_digest_gate_optional", "kind": "sign", - "description": "sign --rust-sip pe succeeds vs native sign when SIGNTOOL_RS_UNSIGNED_FIXTURE + TEST_PFX set; scripts/rust-sip-parity-pe.ps1", + "description": "sign --rust-sip pe succeeds vs native sign when PSIGN_UNSIGNED_FIXTURE + TEST_PFX set; scripts/rust-sip-parity-pe.ps1", "fixture": "tests/fixtures/private/unsigned.exe", "expected": "pending_fixture" }, @@ -164,7 +164,7 @@ { "id": "rust_sip_msi_sign_digest_gate_optional", "kind": "sign", - "description": "sign --rust-sip msi succeeds when SIGNTOOL_RS_MSI_UNSIGNED_FIXTURE + SIGNTOOL_RS_TEST_PFX set; MSI SIP digest vs PKCS#7 after SignerSignEx3", + "description": "sign --rust-sip msi succeeds when PSIGN_MSI_UNSIGNED_FIXTURE + PSIGN_TEST_PFX set; MSI SIP digest vs PKCS#7 after SignerSignEx3", "fixture": "tests/fixtures/private/unsigned.msi", "expected": "pending_fixture" }, diff --git a/tests/parity_signtool.rs b/tests/parity_signtool.rs index 980495e..d1cb055 100644 --- a/tests/parity_signtool.rs +++ b/tests/parity_signtool.rs @@ -20,14 +20,14 @@ fn verify_matches_native_exit_code_for_known_signed_binary() { .output() .expect("failed to execute native signtool"); - let rust = Command::cargo_bin("psign-tool-windows") + let rust = Command::cargo_bin("psign-tool") .expect("binary available") .arg("verify") .arg("--policy") .arg("pa") .arg(fixture) .output() - .expect("failed to execute psign-tool-windows"); + .expect("failed to execute psign-tool"); assert_eq!(native.status.success(), rust.status.success()); } @@ -47,14 +47,14 @@ fn verify_default_policy_matches_native_failure() { .output() .expect("failed to execute native signtool"); - let rust = Command::cargo_bin("psign-tool-windows") + let rust = Command::cargo_bin("psign-tool") .expect("binary available") .arg("verify") .arg("--policy") .arg("default") .arg(fixture) .output() - .expect("failed to execute psign-tool-windows"); + .expect("failed to execute psign-tool"); assert_eq!(native.status.success(), rust.status.success()); } @@ -89,21 +89,21 @@ fn native_signtool_optional_path() -> Option { } #[test] -#[ignore = "requires SIGNTOOL_RS_UNSIGNED_FIXTURE,SIGNTOOL_RS_TEST_PFX,SIGNTOOL_RS_TEST_PFX_PASSWORD"] +#[ignore = "requires PSIGN_UNSIGNED_FIXTURE,PSIGN_TEST_PFX,PSIGN_TEST_PFX_PASSWORD"] fn sign_semantic_parity_creates_verifiable_signature() { - let unsigned = match env_path("SIGNTOOL_RS_UNSIGNED_FIXTURE") { + let unsigned = match env_path("PSIGN_UNSIGNED_FIXTURE") { Some(v) => v, None => return, }; - let pfx = match env_path("SIGNTOOL_RS_TEST_PFX") { + let pfx = match env_path("PSIGN_TEST_PFX") { Some(v) => v, None => return, }; - let pfx_password = env_path("SIGNTOOL_RS_TEST_PFX_PASSWORD"); + let pfx_password = env_path("PSIGN_TEST_PFX_PASSWORD"); let signed_out = std::env::temp_dir().join("psign_signed_semantic.exe"); let _ = std::fs::copy(&unsigned, &signed_out).expect("copy unsigned fixture"); - let mut rust = Command::cargo_bin("psign-tool-windows").expect("binary available"); + let mut rust = Command::cargo_bin("psign-tool").expect("binary available"); rust.arg("sign") .arg("--pfx") .arg(&pfx) @@ -113,7 +113,7 @@ fn sign_semantic_parity_creates_verifiable_signature() { if let Some(p) = pfx_password { rust.arg("--password").arg(p); } - let rust_out = rust.output().expect("run psign-tool-windows sign"); + let rust_out = rust.output().expect("run psign-tool sign"); assert!( rust_out.status.success(), "{}", @@ -134,20 +134,20 @@ fn sign_semantic_parity_creates_verifiable_signature() { } #[test] -#[ignore = "requires SIGNTOOL_RS_SIGNED_FIXTURE and SIGNTOOL_RS_TIMESTAMP_URL"] +#[ignore = "requires PSIGN_SIGNED_FIXTURE and PSIGN_TIMESTAMP_URL"] fn timestamp_semantic_parity_adds_countersignature() { - let signed_fixture = match env_path("SIGNTOOL_RS_SIGNED_FIXTURE") { + let signed_fixture = match env_path("PSIGN_SIGNED_FIXTURE") { Some(v) => v, None => return, }; - let tsa = match env_path("SIGNTOOL_RS_TIMESTAMP_URL") { + let tsa = match env_path("PSIGN_TIMESTAMP_URL") { Some(v) => v, None => return, }; let ts_out = std::env::temp_dir().join("psign_timestamp_semantic.exe"); let _ = std::fs::copy(&signed_fixture, &ts_out).expect("copy signed fixture"); - let rust = Command::cargo_bin("psign-tool-windows") + let rust = Command::cargo_bin("psign-tool") .expect("binary available") .arg("timestamp") .arg("--rfc3161-url") @@ -156,7 +156,7 @@ fn timestamp_semantic_parity_adds_countersignature() { .arg("sha256") .arg(&ts_out) .output() - .expect("run psign-tool-windows timestamp"); + .expect("run psign-tool timestamp"); assert!( rust.status.success(), "{}", @@ -180,17 +180,17 @@ fn timestamp_semantic_parity_adds_countersignature() { } #[test] -#[ignore = "requires SIGNTOOL_RS_DETACHED_CONTENT and SIGNTOOL_RS_DETACHED_PKCS7"] +#[ignore = "requires PSIGN_DETACHED_CONTENT and PSIGN_DETACHED_PKCS7"] fn detached_semantic_parity_matches_native_integrity() { - let content = match env_path("SIGNTOOL_RS_DETACHED_CONTENT") { + let content = match env_path("PSIGN_DETACHED_CONTENT") { Some(v) => v, None => return, }; - let sig = match env_path("SIGNTOOL_RS_DETACHED_PKCS7") { + let sig = match env_path("PSIGN_DETACHED_PKCS7") { Some(v) => v, None => return, }; - let rust = Command::cargo_bin("psign-tool-windows") + let rust = Command::cargo_bin("psign-tool") .expect("binary available") .args(["verify", "--policy", "pa", "--allow-test-root"]) .arg(&content) @@ -206,17 +206,17 @@ fn detached_semantic_parity_matches_native_integrity() { } #[test] -#[ignore = "requires SIGNTOOL_RS_CATALOG_TARGET and SIGNTOOL_RS_CATALOG_FILE"] +#[ignore = "requires PSIGN_CATALOG_TARGET and PSIGN_CATALOG_FILE"] fn catalog_semantic_path_executes_in_rust() { - let target = match env_path("SIGNTOOL_RS_CATALOG_TARGET") { + let target = match env_path("PSIGN_CATALOG_TARGET") { Some(v) => v, None => return, }; - let catalog = match env_path("SIGNTOOL_RS_CATALOG_FILE") { + let catalog = match env_path("PSIGN_CATALOG_FILE") { Some(v) => v, None => return, }; - let rust = Command::cargo_bin("psign-tool-windows") + let rust = Command::cargo_bin("psign-tool") .expect("binary available") .arg("verify") .arg(&target) @@ -230,7 +230,7 @@ fn catalog_semantic_path_executes_in_rust() { String::from_utf8_lossy(&rust.stderr) ); - let rust_os = Command::cargo_bin("psign-tool-windows") + let rust_os = Command::cargo_bin("psign-tool") .expect("binary available") .args(["verify", "--os-version-check", "386:10.0.26100.0"]) .arg(&target) @@ -246,14 +246,14 @@ fn catalog_semantic_path_executes_in_rust() { } #[test] -#[ignore = "requires SIGNTOOL_RS_RDP_TEST_CERT_SHA256; optional SIGNTOOL_RS_RDP_UNSIGNED_FIXTURE"] +#[ignore = "requires PSIGN_RDP_TEST_CERT_SHA256; optional PSIGN_RDP_UNSIGNED_FIXTURE"] fn rdp_sign_writes_native_signature_records() { - let thumbprint = match env_path("SIGNTOOL_RS_RDP_TEST_CERT_SHA256") { + let thumbprint = match env_path("PSIGN_RDP_TEST_CERT_SHA256") { Some(v) => v, None => return, }; let out = std::env::temp_dir().join("psign_rdp_signed_fixture.rdp"); - if let Some(fixture) = env_path("SIGNTOOL_RS_RDP_UNSIGNED_FIXTURE") { + if let Some(fixture) = env_path("PSIGN_RDP_UNSIGNED_FIXTURE") { std::fs::copy(fixture, &out).expect("copy rdp fixture"); } else { std::fs::write( @@ -263,14 +263,14 @@ fn rdp_sign_writes_native_signature_records() { .expect("write rdp fixture"); } - let rust = Command::cargo_bin("psign-tool-windows") + let rust = Command::cargo_bin("psign-tool") .expect("binary available") .arg("rdp") .arg("--sha256") .arg(&thumbprint) .arg(&out) .output() - .expect("run psign-tool-windows rdp"); + .expect("run psign-tool rdp"); assert!( rust.status.success(), "{}{}", @@ -285,13 +285,13 @@ fn rdp_sign_writes_native_signature_records() { } #[test] -#[ignore = "requires SIGNTOOL_RS_MULTISIG_FIXTURE"] +#[ignore = "requires PSIGN_MULTISIG_FIXTURE"] fn multisig_verify_path_executes_in_rust() { - let fixture = match env_path("SIGNTOOL_RS_MULTISIG_FIXTURE") { + let fixture = match env_path("PSIGN_MULTISIG_FIXTURE") { Some(v) => v, None => return, }; - let rust = Command::cargo_bin("psign-tool-windows") + let rust = Command::cargo_bin("psign-tool") .expect("binary available") .arg("verify") .arg(fixture) @@ -306,13 +306,13 @@ fn multisig_verify_path_executes_in_rust() { } #[test] -#[ignore = "requires SIGNTOOL_RS_REVOCATION_FIXTURE"] +#[ignore = "requires PSIGN_REVOCATION_FIXTURE"] fn revocation_policy_path_executes() { - let fixture = match env_path("SIGNTOOL_RS_REVOCATION_FIXTURE") { + let fixture = match env_path("PSIGN_REVOCATION_FIXTURE") { Some(v) => v, None => return, }; - let rust = Command::cargo_bin("psign-tool-windows") + let rust = Command::cargo_bin("psign-tool") .expect("binary available") .arg("verify") .arg(fixture) @@ -325,25 +325,25 @@ fn revocation_policy_path_executes() { } #[test] -#[ignore = "requires SIGNTOOL_RS_MSIX_UNSIGNED_FIXTURE,SIGNTOOL_RS_MSIX_TEST_PFX,SIGNTOOL_RS_MSIX_TIMESTAMP_URL"] +#[ignore = "requires PSIGN_MSIX_UNSIGNED_FIXTURE,PSIGN_MSIX_TEST_PFX,PSIGN_MSIX_TIMESTAMP_URL"] fn msix_sign_with_rfc3161_timestamp_executes() { - let unsigned_msix = match env_path("SIGNTOOL_RS_MSIX_UNSIGNED_FIXTURE") { + let unsigned_msix = match env_path("PSIGN_MSIX_UNSIGNED_FIXTURE") { Some(v) => v, None => return, }; - let pfx = match env_path("SIGNTOOL_RS_MSIX_TEST_PFX") { + let pfx = match env_path("PSIGN_MSIX_TEST_PFX") { Some(v) => v, None => return, }; - let tsa = match env_path("SIGNTOOL_RS_MSIX_TIMESTAMP_URL") { + let tsa = match env_path("PSIGN_MSIX_TIMESTAMP_URL") { Some(v) => v, None => return, }; - let pfx_password = env_path("SIGNTOOL_RS_MSIX_TEST_PFX_PASSWORD"); + let pfx_password = env_path("PSIGN_MSIX_TEST_PFX_PASSWORD"); let out = std::env::temp_dir().join("psign_msix_semantic.msix"); let _ = std::fs::copy(&unsigned_msix, &out).expect("copy unsigned msix"); - let mut rust = Command::cargo_bin("psign-tool-windows").expect("binary available"); + let mut rust = Command::cargo_bin("psign-tool").expect("binary available"); rust.arg("sign") .arg("--pfx") .arg(&pfx) @@ -357,7 +357,7 @@ fn msix_sign_with_rfc3161_timestamp_executes() { rust.arg("--password").arg(pw); } rust.arg(&out); - let rust_out = rust.output().expect("run psign-tool-windows msix sign"); + let rust_out = rust.output().expect("run psign-tool msix sign"); assert!( rust_out.status.success(), "{}", @@ -366,19 +366,19 @@ fn msix_sign_with_rfc3161_timestamp_executes() { } #[test] -#[ignore = "requires SIGNTOOL_RS_MSIX_UNSIGNED_FIXTURE and SIGNTOOL_RS_MSIX_TEST_PFX"] +#[ignore = "requires PSIGN_MSIX_UNSIGNED_FIXTURE and PSIGN_MSIX_TEST_PFX"] fn msix_sign_requires_timestamp_url() { - let unsigned_msix = match env_path("SIGNTOOL_RS_MSIX_UNSIGNED_FIXTURE") { + let unsigned_msix = match env_path("PSIGN_MSIX_UNSIGNED_FIXTURE") { Some(v) => v, None => return, }; - let pfx = match env_path("SIGNTOOL_RS_MSIX_TEST_PFX") { + let pfx = match env_path("PSIGN_MSIX_TEST_PFX") { Some(v) => v, None => return, }; let out = std::env::temp_dir().join("psign_msix_notimestamp.msix"); let _ = std::fs::copy(&unsigned_msix, &out).expect("copy unsigned msix"); - let rust = Command::cargo_bin("psign-tool-windows") + let rust = Command::cargo_bin("psign-tool") .expect("binary available") .arg("sign") .arg("--pfx") @@ -387,38 +387,38 @@ fn msix_sign_requires_timestamp_url() { .arg("sha256") .arg(&out) .output() - .expect("run psign-tool-windows msix sign without timestamp"); + .expect("run psign-tool msix sign without timestamp"); assert!(!rust.status.success()); } #[test] -#[ignore = "requires SIGNTOOL_RS_MSIX_UNSIGNED_FIXTURE,SIGNTOOL_RS_MSIX_TEST_PFX,SIGNTOOL_RS_MSIX_TIMESTAMP_URL,SIGNTOOL_RS_MSIX_DLIB,SIGNTOOL_RS_MSIX_DMDF"] +#[ignore = "requires PSIGN_MSIX_UNSIGNED_FIXTURE,PSIGN_MSIX_TEST_PFX,PSIGN_MSIX_TIMESTAMP_URL,PSIGN_MSIX_DLIB,PSIGN_MSIX_DMDF"] fn msix_dlib_dmdf_path_executes() { - let unsigned_msix = match env_path("SIGNTOOL_RS_MSIX_UNSIGNED_FIXTURE") { + let unsigned_msix = match env_path("PSIGN_MSIX_UNSIGNED_FIXTURE") { Some(v) => v, None => return, }; - let pfx = match env_path("SIGNTOOL_RS_MSIX_TEST_PFX") { + let pfx = match env_path("PSIGN_MSIX_TEST_PFX") { Some(v) => v, None => return, }; - let tsa = match env_path("SIGNTOOL_RS_MSIX_TIMESTAMP_URL") { + let tsa = match env_path("PSIGN_MSIX_TIMESTAMP_URL") { Some(v) => v, None => return, }; - let dlib = match env_path("SIGNTOOL_RS_MSIX_DLIB") { + let dlib = match env_path("PSIGN_MSIX_DLIB") { Some(v) => v, None => return, }; - let dmdf = match env_path("SIGNTOOL_RS_MSIX_DMDF") { + let dmdf = match env_path("PSIGN_MSIX_DMDF") { Some(v) => v, None => return, }; - let pfx_password = env_path("SIGNTOOL_RS_MSIX_TEST_PFX_PASSWORD"); + let pfx_password = env_path("PSIGN_MSIX_TEST_PFX_PASSWORD"); let out = std::env::temp_dir().join("psign_msix_decoupled.msix"); let _ = std::fs::copy(&unsigned_msix, &out).expect("copy unsigned msix"); - let mut rust = Command::cargo_bin("psign-tool-windows").expect("binary available"); + let mut rust = Command::cargo_bin("psign-tool").expect("binary available"); rust.arg("sign") .arg("--pfx") .arg(&pfx) @@ -437,9 +437,7 @@ fn msix_dlib_dmdf_path_executes() { rust.arg("--password").arg(pw); } rust.arg(&out); - let rust_out = rust - .output() - .expect("run psign-tool-windows msix dlib/dmdf sign"); + let rust_out = rust.output().expect("run psign-tool msix dlib/dmdf sign"); assert!( rust_out.status.success(), "{}", @@ -448,34 +446,34 @@ fn msix_dlib_dmdf_path_executes() { } #[test] -#[ignore = "requires SIGNTOOL_RS_ARTIFACT_SIGNING_UNSIGNED_PE,SIGNTOOL_RS_ARTIFACT_SIGNING_METADATA,SIGNTOOL_RS_ARTIFACT_SIGNING_TIMESTAMP_URL,SIGNTOOL_RS_ARTIFACT_SIGNING_TEST_PFX and SIGNTOOL_RS_ARTIFACT_SIGNING_DLIB or SIGNTOOL_RS_ARTIFACT_SIGNING_DLIB_ROOT"] +#[ignore = "requires PSIGN_ARTIFACT_SIGNING_UNSIGNED_PE,PSIGN_ARTIFACT_SIGNING_METADATA,PSIGN_ARTIFACT_SIGNING_TIMESTAMP_URL,PSIGN_ARTIFACT_SIGNING_TEST_PFX and PSIGN_ARTIFACT_SIGNING_DLIB or PSIGN_ARTIFACT_SIGNING_DLIB_ROOT"] fn artifact_signing_decoupled_pe_executes() { - let unsigned_pe = match env_path("SIGNTOOL_RS_ARTIFACT_SIGNING_UNSIGNED_PE") { + let unsigned_pe = match env_path("PSIGN_ARTIFACT_SIGNING_UNSIGNED_PE") { Some(v) => v, None => return, }; - let metadata = match env_path("SIGNTOOL_RS_ARTIFACT_SIGNING_METADATA") { + let metadata = match env_path("PSIGN_ARTIFACT_SIGNING_METADATA") { Some(v) => v, None => return, }; - let tsa = match env_path("SIGNTOOL_RS_ARTIFACT_SIGNING_TIMESTAMP_URL") { + let tsa = match env_path("PSIGN_ARTIFACT_SIGNING_TIMESTAMP_URL") { Some(v) => v, None => return, }; - let pfx = match env_path("SIGNTOOL_RS_ARTIFACT_SIGNING_TEST_PFX") { + let pfx = match env_path("PSIGN_ARTIFACT_SIGNING_TEST_PFX") { Some(v) => v, None => return, }; - let dlib = env_path("SIGNTOOL_RS_ARTIFACT_SIGNING_DLIB"); - let dlib_root = env_path("SIGNTOOL_RS_ARTIFACT_SIGNING_DLIB_ROOT"); + let dlib = env_path("PSIGN_ARTIFACT_SIGNING_DLIB"); + let dlib_root = env_path("PSIGN_ARTIFACT_SIGNING_DLIB_ROOT"); if dlib.is_none() && dlib_root.is_none() { return; } - let pfx_password = env_path("SIGNTOOL_RS_ARTIFACT_SIGNING_TEST_PFX_PASSWORD"); + let pfx_password = env_path("PSIGN_ARTIFACT_SIGNING_TEST_PFX_PASSWORD"); let out = std::env::temp_dir().join("psign_artifact_signing_decoupled.exe"); let _ = std::fs::copy(&unsigned_pe, &out).expect("copy unsigned pe"); - let mut rust = Command::cargo_bin("psign-tool-windows").expect("binary available"); + let mut rust = Command::cargo_bin("psign-tool").expect("binary available"); rust.arg("sign") .arg("--pfx") .arg(&pfx) @@ -499,7 +497,7 @@ fn artifact_signing_decoupled_pe_executes() { rust.arg(&out); let rust_out = rust .output() - .expect("run psign-tool-windows artifact signing decoupled sign"); + .expect("run psign-tool artifact signing decoupled sign"); assert!( rust_out.status.success(), "{}", @@ -508,27 +506,27 @@ fn artifact_signing_decoupled_pe_executes() { } #[test] -#[ignore = "requires SIGNTOOL_RS_UNSIGNED_FIXTURE,SIGNTOOL_RS_TEST_PFX,SIGNTOOL_RS_TIMESTAMP_URL"] +#[ignore = "requires PSIGN_UNSIGNED_FIXTURE,PSIGN_TEST_PFX,PSIGN_TIMESTAMP_URL"] fn append_signature_pe_nested_pkcs7_visible_to_inspector() { - let unsigned = match env_path("SIGNTOOL_RS_UNSIGNED_FIXTURE") { + let unsigned = match env_path("PSIGN_UNSIGNED_FIXTURE") { Some(v) => v, None => return, }; - let pfx = match env_path("SIGNTOOL_RS_TEST_PFX") { + let pfx = match env_path("PSIGN_TEST_PFX") { Some(v) => v, None => return, }; - let tsa = match env_path("SIGNTOOL_RS_TIMESTAMP_URL") { + let tsa = match env_path("PSIGN_TIMESTAMP_URL") { Some(v) => v, None => return, }; - let pfx_password = env_path("SIGNTOOL_RS_TEST_PFX_PASSWORD"); + let pfx_password = env_path("PSIGN_TEST_PFX_PASSWORD"); let first = std::env::temp_dir().join("psign_append_inspect_a.exe"); let second = std::env::temp_dir().join("psign_append_inspect_b.exe"); let _ = std::fs::copy(&unsigned, &first).expect("copy unsigned first"); let _ = std::fs::copy(&unsigned, &second).expect("copy unsigned second"); - let mut one = Command::cargo_bin("psign-tool-windows").expect("binary available"); + let mut one = Command::cargo_bin("psign-tool").expect("binary available"); one.arg("sign") .arg("--pfx") .arg(&pfx) @@ -551,7 +549,7 @@ fn append_signature_pe_nested_pkcs7_visible_to_inspector() { ); let _ = std::fs::copy(&first, &second).expect("copy signed to second before append"); - let mut two = Command::cargo_bin("psign-tool-windows").expect("binary available"); + let mut two = Command::cargo_bin("psign-tool").expect("binary available"); two.arg("sign") .arg("--pfx") .arg(&pfx) @@ -586,16 +584,16 @@ fn append_signature_pe_nested_pkcs7_visible_to_inspector() { /// PowerShell `.ps1`: same Windows SIP stack as native (`SignerSignEx3`). Bytes may differ (PKCS#7 /// encoding); native `verify /pa` must accept the Rust-signed file. #[test] -#[ignore = "requires SIGNTOOL_RS_TEST_PFX; native signtool.exe; optional SIGNTOOL_RS_TEST_PFX_PASSWORD and SIGNTOOL_RS_PS1_UNSIGNED_FIXTURE"] +#[ignore = "requires PSIGN_TEST_PFX; native signtool.exe; optional PSIGN_TEST_PFX_PASSWORD and PSIGN_PS1_UNSIGNED_FIXTURE"] fn ps1_sign_aligns_with_native_sip_stack() { - let pfx = match env_path("SIGNTOOL_RS_TEST_PFX") { + let pfx = match env_path("PSIGN_TEST_PFX") { Some(v) => v, None => return, }; let Some(native_exe) = native_signtool_optional_path() else { return; }; - let ps1_src = env_path("SIGNTOOL_RS_PS1_UNSIGNED_FIXTURE") + let ps1_src = env_path("PSIGN_PS1_UNSIGNED_FIXTURE") .map(PathBuf::from) .filter(|p| p.exists()) .unwrap_or_else(|| { @@ -609,7 +607,7 @@ fn ps1_sign_aligns_with_native_sip_stack() { let _ = std::fs::copy(&ps1_src, &tmp_nat).expect("copy native ps1 temp"); let _ = std::fs::copy(&ps1_src, &tmp_rust).expect("copy rust ps1 temp"); - let pw = env_path("SIGNTOOL_RS_TEST_PFX_PASSWORD"); + let pw = env_path("PSIGN_TEST_PFX_PASSWORD"); let mut native_cmd = Command::new(&native_exe); native_cmd @@ -629,7 +627,7 @@ fn ps1_sign_aligns_with_native_sip_stack() { String::from_utf8_lossy(&native_out.stderr) ); - let mut rust_cmd = Command::cargo_bin("psign-tool-windows").expect("binary available"); + let mut rust_cmd = Command::cargo_bin("psign-tool").expect("binary available"); rust_cmd .arg("sign") .arg("--pfx") @@ -679,16 +677,16 @@ fn ps1_sign_aligns_with_native_sip_stack() { /// PowerShell `.psm1`: same Windows SIP as `.ps1`. #[test] -#[ignore = "requires SIGNTOOL_RS_TEST_PFX; native signtool.exe; optional SIGNTOOL_RS_TEST_PFX_PASSWORD and SIGNTOOL_RS_PSM1_UNSIGNED_FIXTURE"] +#[ignore = "requires PSIGN_TEST_PFX; native signtool.exe; optional PSIGN_TEST_PFX_PASSWORD and PSIGN_PSM1_UNSIGNED_FIXTURE"] fn psm1_sign_aligns_with_native_sip_stack() { - let pfx = match env_path("SIGNTOOL_RS_TEST_PFX") { + let pfx = match env_path("PSIGN_TEST_PFX") { Some(v) => v, None => return, }; let Some(native_exe) = native_signtool_optional_path() else { return; }; - let src = env_path("SIGNTOOL_RS_PSM1_UNSIGNED_FIXTURE") + let src = env_path("PSIGN_PSM1_UNSIGNED_FIXTURE") .map(PathBuf::from) .filter(|p| p.exists()) .unwrap_or_else(|| { @@ -702,7 +700,7 @@ fn psm1_sign_aligns_with_native_sip_stack() { let _ = std::fs::copy(&src, &tmp_nat).expect("copy native psm1 temp"); let _ = std::fs::copy(&src, &tmp_rust).expect("copy rust psm1 temp"); - let pw = env_path("SIGNTOOL_RS_TEST_PFX_PASSWORD"); + let pw = env_path("PSIGN_TEST_PFX_PASSWORD"); let mut native_cmd = Command::new(&native_exe); native_cmd @@ -722,7 +720,7 @@ fn psm1_sign_aligns_with_native_sip_stack() { String::from_utf8_lossy(&native_out.stderr) ); - let mut rust_cmd = Command::cargo_bin("psign-tool-windows").expect("binary available"); + let mut rust_cmd = Command::cargo_bin("psign-tool").expect("binary available"); rust_cmd .arg("sign") .arg("--pfx") @@ -772,16 +770,16 @@ fn psm1_sign_aligns_with_native_sip_stack() { /// PowerShell manifest `.psd1`: same Windows SIP as `.ps1`. #[test] -#[ignore = "requires SIGNTOOL_RS_TEST_PFX; native signtool.exe; optional SIGNTOOL_RS_TEST_PFX_PASSWORD and SIGNTOOL_RS_PSD1_UNSIGNED_FIXTURE"] +#[ignore = "requires PSIGN_TEST_PFX; native signtool.exe; optional PSIGN_TEST_PFX_PASSWORD and PSIGN_PSD1_UNSIGNED_FIXTURE"] fn psd1_sign_aligns_with_native_sip_stack() { - let pfx = match env_path("SIGNTOOL_RS_TEST_PFX") { + let pfx = match env_path("PSIGN_TEST_PFX") { Some(v) => v, None => return, }; let Some(native_exe) = native_signtool_optional_path() else { return; }; - let src = env_path("SIGNTOOL_RS_PSD1_UNSIGNED_FIXTURE") + let src = env_path("PSIGN_PSD1_UNSIGNED_FIXTURE") .map(PathBuf::from) .filter(|p| p.exists()) .unwrap_or_else(|| { @@ -795,7 +793,7 @@ fn psd1_sign_aligns_with_native_sip_stack() { let _ = std::fs::copy(&src, &tmp_nat).expect("copy native psd1 temp"); let _ = std::fs::copy(&src, &tmp_rust).expect("copy rust psd1 temp"); - let pw = env_path("SIGNTOOL_RS_TEST_PFX_PASSWORD"); + let pw = env_path("PSIGN_TEST_PFX_PASSWORD"); let mut native_cmd = Command::new(&native_exe); native_cmd @@ -815,7 +813,7 @@ fn psd1_sign_aligns_with_native_sip_stack() { String::from_utf8_lossy(&native_out.stderr) ); - let mut rust_cmd = Command::cargo_bin("psign-tool-windows").expect("binary available"); + let mut rust_cmd = Command::cargo_bin("psign-tool").expect("binary available"); rust_cmd .arg("sign") .arg("--pfx") @@ -865,16 +863,16 @@ fn psd1_sign_aligns_with_native_sip_stack() { /// Windows Installer `.msi`: OS SIP (`msisip.dll`); same `SignerSignEx3` / `WinVerifyTrust` stack as native. #[test] -#[ignore = "requires SIGNTOOL_RS_MSI_UNSIGNED_FIXTURE and SIGNTOOL_RS_TEST_PFX; native signtool.exe; optional SIGNTOOL_RS_TEST_PFX_PASSWORD and SIGNTOOL_RS_MSI_TIMESTAMP_URL"] +#[ignore = "requires PSIGN_MSI_UNSIGNED_FIXTURE and PSIGN_TEST_PFX; native signtool.exe; optional PSIGN_TEST_PFX_PASSWORD and PSIGN_MSI_TIMESTAMP_URL"] fn msi_sign_aligns_with_native_sip_stack() { - let msi_src = match env_path("SIGNTOOL_RS_MSI_UNSIGNED_FIXTURE") { + let msi_src = match env_path("PSIGN_MSI_UNSIGNED_FIXTURE") { Some(v) => PathBuf::from(v), None => return, }; if !msi_src.exists() { return; } - let pfx = match env_path("SIGNTOOL_RS_TEST_PFX") { + let pfx = match env_path("PSIGN_TEST_PFX") { Some(v) => v, None => return, }; @@ -886,8 +884,8 @@ fn msi_sign_aligns_with_native_sip_stack() { let _ = std::fs::copy(&msi_src, &tmp_nat).expect("copy native msi temp"); let _ = std::fs::copy(&msi_src, &tmp_rust).expect("copy rust msi temp"); - let pw = env_path("SIGNTOOL_RS_TEST_PFX_PASSWORD"); - let ts = env_path("SIGNTOOL_RS_MSI_TIMESTAMP_URL"); + let pw = env_path("PSIGN_TEST_PFX_PASSWORD"); + let ts = env_path("PSIGN_MSI_TIMESTAMP_URL"); let mut native_cmd = Command::new(&native_exe); native_cmd @@ -910,7 +908,7 @@ fn msi_sign_aligns_with_native_sip_stack() { String::from_utf8_lossy(&native_out.stderr) ); - let mut rust_cmd = Command::cargo_bin("psign-tool-windows").expect("binary available"); + let mut rust_cmd = Command::cargo_bin("psign-tool").expect("binary available"); rust_cmd .arg("sign") .arg("--pfx") @@ -967,16 +965,16 @@ fn msi_sign_aligns_with_native_sip_stack() { /// Windows metadata `.winmd`: PE-based CLI assembly; OS Authenticode SIP (`SignerSignEx3` / `WinVerifyTrust`). #[test] -#[ignore = "requires SIGNTOOL_RS_WINMD_UNSIGNED_FIXTURE and SIGNTOOL_RS_TEST_PFX; native signtool.exe; optional SIGNTOOL_RS_TEST_PFX_PASSWORD and SIGNTOOL_RS_WINMD_TIMESTAMP_URL"] +#[ignore = "requires PSIGN_WINMD_UNSIGNED_FIXTURE and PSIGN_TEST_PFX; native signtool.exe; optional PSIGN_TEST_PFX_PASSWORD and PSIGN_WINMD_TIMESTAMP_URL"] fn winmd_sign_aligns_with_native_sip_stack() { - let winmd_src = match env_path("SIGNTOOL_RS_WINMD_UNSIGNED_FIXTURE") { + let winmd_src = match env_path("PSIGN_WINMD_UNSIGNED_FIXTURE") { Some(v) => PathBuf::from(v), None => return, }; if !winmd_src.exists() { return; } - let pfx = match env_path("SIGNTOOL_RS_TEST_PFX") { + let pfx = match env_path("PSIGN_TEST_PFX") { Some(v) => v, None => return, }; @@ -988,8 +986,8 @@ fn winmd_sign_aligns_with_native_sip_stack() { let _ = std::fs::copy(&winmd_src, &tmp_nat).expect("copy native winmd temp"); let _ = std::fs::copy(&winmd_src, &tmp_rust).expect("copy rust winmd temp"); - let pw = env_path("SIGNTOOL_RS_TEST_PFX_PASSWORD"); - let ts = env_path("SIGNTOOL_RS_WINMD_TIMESTAMP_URL"); + let pw = env_path("PSIGN_TEST_PFX_PASSWORD"); + let ts = env_path("PSIGN_WINMD_TIMESTAMP_URL"); let mut native_cmd = Command::new(&native_exe); native_cmd @@ -1012,7 +1010,7 @@ fn winmd_sign_aligns_with_native_sip_stack() { String::from_utf8_lossy(&native_out.stderr) ); - let mut rust_cmd = Command::cargo_bin("psign-tool-windows").expect("binary available"); + let mut rust_cmd = Command::cargo_bin("psign-tool").expect("binary available"); rust_cmd .arg("sign") .arg("--pfx") @@ -1069,16 +1067,16 @@ fn winmd_sign_aligns_with_native_sip_stack() { /// Windows Script Host `.js`: OS SIP when registered (same stack as native `signtool`). #[test] -#[ignore = "requires SIGNTOOL_RS_TEST_PFX; native signtool.exe; optional SIGNTOOL_RS_TEST_PFX_PASSWORD and SIGNTOOL_RS_JS_UNSIGNED_FIXTURE"] +#[ignore = "requires PSIGN_TEST_PFX; native signtool.exe; optional PSIGN_TEST_PFX_PASSWORD and PSIGN_JS_UNSIGNED_FIXTURE"] fn js_sign_aligns_with_native_sip_stack() { - let pfx = match env_path("SIGNTOOL_RS_TEST_PFX") { + let pfx = match env_path("PSIGN_TEST_PFX") { Some(v) => v, None => return, }; let Some(native_exe) = native_signtool_optional_path() else { return; }; - let js_src = env_path("SIGNTOOL_RS_JS_UNSIGNED_FIXTURE") + let js_src = env_path("PSIGN_JS_UNSIGNED_FIXTURE") .map(PathBuf::from) .filter(|p| p.exists()) .unwrap_or_else(|| { @@ -1092,7 +1090,7 @@ fn js_sign_aligns_with_native_sip_stack() { let _ = std::fs::copy(&js_src, &tmp_nat).expect("copy native js temp"); let _ = std::fs::copy(&js_src, &tmp_rust).expect("copy rust js temp"); - let pw = env_path("SIGNTOOL_RS_TEST_PFX_PASSWORD"); + let pw = env_path("PSIGN_TEST_PFX_PASSWORD"); let mut native_cmd = Command::new(&native_exe); native_cmd @@ -1112,7 +1110,7 @@ fn js_sign_aligns_with_native_sip_stack() { String::from_utf8_lossy(&native_out.stderr) ); - let mut rust_cmd = Command::cargo_bin("psign-tool-windows").expect("binary available"); + let mut rust_cmd = Command::cargo_bin("psign-tool").expect("binary available"); rust_cmd .arg("sign") .arg("--pfx") @@ -1161,16 +1159,16 @@ fn js_sign_aligns_with_native_sip_stack() { } #[test] -#[ignore = "requires SIGNTOOL_RS_TEST_PFX; native signtool.exe; optional SIGNTOOL_RS_TEST_PFX_PASSWORD and SIGNTOOL_RS_VBS_UNSIGNED_FIXTURE"] +#[ignore = "requires PSIGN_TEST_PFX; native signtool.exe; optional PSIGN_TEST_PFX_PASSWORD and PSIGN_VBS_UNSIGNED_FIXTURE"] fn vbs_sign_aligns_with_native_sip_stack() { - let pfx = match env_path("SIGNTOOL_RS_TEST_PFX") { + let pfx = match env_path("PSIGN_TEST_PFX") { Some(v) => v, None => return, }; let Some(native_exe) = native_signtool_optional_path() else { return; }; - let vbs_src = env_path("SIGNTOOL_RS_VBS_UNSIGNED_FIXTURE") + let vbs_src = env_path("PSIGN_VBS_UNSIGNED_FIXTURE") .map(PathBuf::from) .filter(|p| p.exists()) .unwrap_or_else(|| { @@ -1184,7 +1182,7 @@ fn vbs_sign_aligns_with_native_sip_stack() { let _ = std::fs::copy(&vbs_src, &tmp_nat).expect("copy native vbs temp"); let _ = std::fs::copy(&vbs_src, &tmp_rust).expect("copy rust vbs temp"); - let pw = env_path("SIGNTOOL_RS_TEST_PFX_PASSWORD"); + let pw = env_path("PSIGN_TEST_PFX_PASSWORD"); let mut native_cmd = Command::new(&native_exe); native_cmd @@ -1204,7 +1202,7 @@ fn vbs_sign_aligns_with_native_sip_stack() { String::from_utf8_lossy(&native_out.stderr) ); - let mut rust_cmd = Command::cargo_bin("psign-tool-windows").expect("binary available"); + let mut rust_cmd = Command::cargo_bin("psign-tool").expect("binary available"); rust_cmd .arg("sign") .arg("--pfx") @@ -1253,16 +1251,16 @@ fn vbs_sign_aligns_with_native_sip_stack() { } #[test] -#[ignore = "requires SIGNTOOL_RS_TEST_PFX; native signtool.exe; optional SIGNTOOL_RS_TEST_PFX_PASSWORD and SIGNTOOL_RS_WSF_UNSIGNED_FIXTURE"] +#[ignore = "requires PSIGN_TEST_PFX; native signtool.exe; optional PSIGN_TEST_PFX_PASSWORD and PSIGN_WSF_UNSIGNED_FIXTURE"] fn wsf_sign_aligns_with_native_sip_stack() { - let pfx = match env_path("SIGNTOOL_RS_TEST_PFX") { + let pfx = match env_path("PSIGN_TEST_PFX") { Some(v) => v, None => return, }; let Some(native_exe) = native_signtool_optional_path() else { return; }; - let wsf_src = env_path("SIGNTOOL_RS_WSF_UNSIGNED_FIXTURE") + let wsf_src = env_path("PSIGN_WSF_UNSIGNED_FIXTURE") .map(PathBuf::from) .filter(|p| p.exists()) .unwrap_or_else(|| { @@ -1276,7 +1274,7 @@ fn wsf_sign_aligns_with_native_sip_stack() { let _ = std::fs::copy(&wsf_src, &tmp_nat).expect("copy native wsf temp"); let _ = std::fs::copy(&wsf_src, &tmp_rust).expect("copy rust wsf temp"); - let pw = env_path("SIGNTOOL_RS_TEST_PFX_PASSWORD"); + let pw = env_path("PSIGN_TEST_PFX_PASSWORD"); let mut native_cmd = Command::new(&native_exe); native_cmd @@ -1296,7 +1294,7 @@ fn wsf_sign_aligns_with_native_sip_stack() { String::from_utf8_lossy(&native_out.stderr) ); - let mut rust_cmd = Command::cargo_bin("psign-tool-windows").expect("binary available"); + let mut rust_cmd = Command::cargo_bin("psign-tool").expect("binary available"); rust_cmd .arg("sign") .arg("--pfx") @@ -1346,30 +1344,30 @@ fn wsf_sign_aligns_with_native_sip_stack() { #[cfg(feature = "artifact-signing-rest")] #[test] -#[ignore = "requires SIGNTOOL_RS_ARTIFACT_SIGNING_REST_REGION, ACCOUNT_NAME, PROFILE_NAME, DIGEST_FILE and auth (see docs/migration-artifact-signing.md#rest-hash-signing-gated-smoke-test)"] +#[ignore = "requires PSIGN_ARTIFACT_SIGNING_REST_REGION, ACCOUNT_NAME, PROFILE_NAME, DIGEST_FILE and auth (see docs/migration-artifact-signing.md#rest-hash-signing-gated-smoke-test)"] fn artifact_signing_rest_submit_smoke() { - let region = match env_path("SIGNTOOL_RS_ARTIFACT_SIGNING_REST_REGION") { + let region = match env_path("PSIGN_ARTIFACT_SIGNING_REST_REGION") { Some(v) => v, None => return, }; - let account_name = match env_path("SIGNTOOL_RS_ARTIFACT_SIGNING_REST_ACCOUNT_NAME") { + let account_name = match env_path("PSIGN_ARTIFACT_SIGNING_REST_ACCOUNT_NAME") { Some(v) => v, None => return, }; - let profile_name = match env_path("SIGNTOOL_RS_ARTIFACT_SIGNING_REST_PROFILE_NAME") { + let profile_name = match env_path("PSIGN_ARTIFACT_SIGNING_REST_PROFILE_NAME") { Some(v) => v, None => return, }; - let digest_file = match env_path("SIGNTOOL_RS_ARTIFACT_SIGNING_REST_DIGEST_FILE") { + let digest_file = match env_path("PSIGN_ARTIFACT_SIGNING_REST_DIGEST_FILE") { Some(v) => v, None => return, }; - let access_token = env_path("SIGNTOOL_RS_ARTIFACT_SIGNING_REST_ACCESS_TOKEN"); - let mi = env_flag_true("SIGNTOOL_RS_ARTIFACT_SIGNING_REST_MANAGED_IDENTITY"); - let tenant = env_path("SIGNTOOL_RS_ARTIFACT_SIGNING_REST_TENANT_ID"); - let client_id = env_path("SIGNTOOL_RS_ARTIFACT_SIGNING_REST_CLIENT_ID"); - let client_secret = env_path("SIGNTOOL_RS_ARTIFACT_SIGNING_REST_CLIENT_SECRET"); + let access_token = env_path("PSIGN_ARTIFACT_SIGNING_REST_ACCESS_TOKEN"); + let mi = env_flag_true("PSIGN_ARTIFACT_SIGNING_REST_MANAGED_IDENTITY"); + let tenant = env_path("PSIGN_ARTIFACT_SIGNING_REST_TENANT_ID"); + let client_id = env_path("PSIGN_ARTIFACT_SIGNING_REST_CLIENT_ID"); + let client_secret = env_path("PSIGN_ARTIFACT_SIGNING_REST_CLIENT_SECRET"); let auth_ready = access_token.is_some() || mi @@ -1378,7 +1376,7 @@ fn artifact_signing_rest_submit_smoke() { return; } - let mut cmd = Command::cargo_bin("psign-tool-windows").expect("binary available"); + let mut cmd = Command::cargo_bin("psign-tool").expect("binary available"); cmd.arg("artifact-signing-submit") .arg("--region") .arg(®ion) @@ -1389,7 +1387,7 @@ fn artifact_signing_rest_submit_smoke() { .arg("--digest-file") .arg(&digest_file); - if let Some(alg) = env_path("SIGNTOOL_RS_ARTIFACT_SIGNING_REST_SIGNATURE_ALGORITHM") { + if let Some(alg) = env_path("PSIGN_ARTIFACT_SIGNING_REST_SIGNATURE_ALGORITHM") { cmd.arg("--signature-algorithm").arg(&alg); } diff --git a/tests/sip_rust_pe.rs b/tests/sip_rust_pe.rs index 70ccd80..b0cf334 100644 --- a/tests/sip_rust_pe.rs +++ b/tests/sip_rust_pe.rs @@ -17,12 +17,12 @@ fn upstream_signed_tiny64_digest_consistency() { } #[test] -#[ignore = "set SIGNTOOL_RS_SIGNED_FIXTURE to a WinTrust-verifiable signed PE"] +#[ignore = "set PSIGN_SIGNED_FIXTURE to a WinTrust-verifiable signed PE"] fn signed_fixture_digest_check_after_trust() { - let path = std::env::var_os("SIGNTOOL_RS_SIGNED_FIXTURE") - .expect("SIGNTOOL_RS_SIGNED_FIXTURE must be set when running this ignored test"); + let path = std::env::var_os("PSIGN_SIGNED_FIXTURE") + .expect("PSIGN_SIGNED_FIXTURE must be set when running this ignored test"); let path = std::path::Path::new(&path); - let mut verify = assert_cmd::Command::cargo_bin("psign-tool-windows").expect("binary"); + let mut verify = assert_cmd::Command::cargo_bin("psign-tool").expect("binary"); verify.args([ "verify", "--policy", From 2902e97fa802aac81ca637103471c9a0f1a8515d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andr=C3=A9=20Moreau?= Date: Wed, 13 May 2026 13:03:55 -0400 Subject: [PATCH 2/2] Fix rust SIP parity workflow YAML Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .github/workflows/rust-sip-parity.yml | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/.github/workflows/rust-sip-parity.yml b/.github/workflows/rust-sip-parity.yml index 22deaaf..30df20b 100644 --- a/.github/workflows/rust-sip-parity.yml +++ b/.github/workflows/rust-sip-parity.yml @@ -27,7 +27,7 @@ jobs: - name: CAB RS256 prehash + extract (signed fixture + unsigned errors) run: cargo test -p psign-digest-cli --locked cab_rs256_ - - name: CAB RS256 prehash helper (library: signed fixture + unsigned rejection) + - name: "CAB RS256 prehash helper (library: signed fixture + unsigned rejection)" run: cargo test -p psign-sip-digest --lib cab_rsa_sha256_signer_prehash --locked - name: MSI PKCS#7 extract + RS256 prehash CLI (OLE stub vs PE parity) @@ -58,12 +58,22 @@ jobs: run: cargo test -p psign-authenticode-trust --lib detached_trust_ --locked - name: RFC3161 TimeStampReq/Resp + timestamp policy (library + CLI) - run: cargo test -p psign-sip-digest --lib timestamp:: --locked && cargo test -p psign-authenticode-trust --lib tiny32_upstream --locked && cargo test -p psign-authenticode-trust --lib tiny64_upstream_pe_pkcs7_pkcs9_signing_time_extracts --locked && cargo test -p psign-authenticode-trust --lib tiny64_upstream_bare_signed --locked && cargo test -p psign-authenticode-trust --lib nested_tstinfo --locked && cargo test -p psign-authenticode-trust --lib time_rejects --locked && cargo test -p psign-authenticode-trust --lib resolve_verification_ --locked && cargo test -p psign-digest-cli --locked trust_verify_pe_ok_require_valid_timestamp_pkcs9_signing_time_on_tiny32 && cargo test -p psign-digest-cli --locked trust_verify_pe_ok_require_valid_timestamp_pkcs9_signing_time_on_tiny64 && cargo test -p psign-digest-cli --locked portable_rfc3161_timestamp + run: | + cargo test -p psign-sip-digest --lib timestamp:: --locked + cargo test -p psign-authenticode-trust --lib tiny32_upstream --locked + cargo test -p psign-authenticode-trust --lib tiny64_upstream_pe_pkcs7_pkcs9_signing_time_extracts --locked + cargo test -p psign-authenticode-trust --lib tiny64_upstream_bare_signed --locked + cargo test -p psign-authenticode-trust --lib nested_tstinfo --locked + cargo test -p psign-authenticode-trust --lib time_rejects --locked + cargo test -p psign-authenticode-trust --lib resolve_verification_ --locked + cargo test -p psign-digest-cli --locked trust_verify_pe_ok_require_valid_timestamp_pkcs9_signing_time_on_tiny32 + cargo test -p psign-digest-cli --locked trust_verify_pe_ok_require_valid_timestamp_pkcs9_signing_time_on_tiny64 + cargo test -p psign-digest-cli --locked portable_rfc3161_timestamp - name: Build psign-tool portable (timestamp-http feature, compile-only) run: cargo build -p psign-digest-cli --features timestamp-http --locked - - name: Artifact Signing REST (library + mock :sign LRO) + - name: "Artifact Signing REST (library + mock :sign LRO)" run: cargo test -p psign-codesigning-rest --locked - name: Azure Key Vault REST helpers (library unit tests)