WIP: ci(windows): optionally sign builder executables with Authenticode #98
No reviewers
Labels
No labels
accessibility
autorelease: pending
autorelease: tagged
bug
dependencies
documentation
duplicate
enhancement
github_actions
good first issue
help wanted
invalid
needs measurements
question
testing wanted
wontfix
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
MagicBeanDev/Longshot!98
Loading…
Reference in a new issue
No description provided.
Delete branch "mooki/Longshot:ci/windows-authenticode"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Why
Signed release checksums do not give the Windows builder a verified publisher. This prepares optional Authenticode signing before packaging, without buying a certificate, choosing a legal publisher, or claiming that SmartScreen warnings are solved.
What changed
scripts/authenticode.py:certificate_forpermits only this repository's main/version-tag pushes; absent configuration logsnot signed: no certificate configuredand succeeds unsigned.sign_buildernames only the setup and the published WPF apphost, uses Windows' built-in PowerShell to sign with SHA256 and a timestamp, then requires a trusted embedded signature by the selected certificate and a timestamp certificate. Failed signing or verification stops packaging. Provider output is never echoed..forgejo/workflows/release.ymlandbuild-windows.yml: explicit optional certificate-selector secret, boolean configuration output, release-only runner selection and signing after compilation/publish but before both setup copies and the zip. The triggering commit must match. PRs and manual build dispatches do not sign. No action pins,tools.lock,scripts/tools.py, or checksum-signature rules changed; no new tool is downloaded in CI.tests/test_authenticode.py: 12 portable decision/command/failure/packaging tests, with a stand-in subprocess result rather than real Windows signing.tests/authenticode_standin.py: an explicit Linux mechanics test with a temporary self-signed certificate and authored PE. It verifies embedded signatures in the three package paths and rejects altered code. It does not exercise Windows PowerShell, a provider, a timestamp server or public publisher trust.docs/releases.md,RELEASING.md,docs/forgejo-runners.md: separate the two signature layers, inventory the files, explain setup/rotation and source the certificate/cost choices. A signing runner's label is not an access control: its admission and provider authorization must exclude PR workloads before any identity is provisioned.This covers two unique Longshot builder executables under three zip paths:
LongshotBuilder.exe(setup),LongshotBuilder/LongshotBuilder-Setup.exe(Repair's identical copy), andLongshotBuilder/LongshotBuilder.exe(WPF apphost). Third-party executables are untouched. The launcher is built in its own repository and needs its own integration. The player's locally compiled game is not signed by this step.Flags and settings
AUTHENTICODE_CERTIFICATE_SHA1build-windows.ymlinputauthenticodeauthenticode.py plan/sign --setup ... --publish ...authenticode_standin.py --osslsigncode ...The timestamp URL is
http://timestamp.digicert.com. This is the Windows cmdlet's documented HTTP Authenticode protocol, not a new RFC 3161 client; a missing timestamp fails verification. Private keys stay with the chosen provider. Azure/SignPath service APIs are not implemented by this certificate-store route.Measurements
Base:
77f605a25fHead:
57698f8b0fSuite totals: main 509 passed, 0 failed, 19 skipped; this PR 510 passed, 0 failed, 19 skipped.
main:
77f605a25(2026-11-13 UTC)this PR: move/ci/windows-authenticode @
57698f8bHardware: Linux x86-64, kernel 7.2.15-1-omarchy, Python 3.14.8; clang/LLD 23.1.3 for the authored PE
Scene: game-free packaging; no GPU or gameplay measurement
Method: one complete source-suite result per commit, named unit tests, and one cryptographic stand-in. These are correctness checks, not an interleaved performance comparison or a Windows trust measurement.
How to reproduce:
python -I -B tests/run_all.py --jobs 6 --strict;python -I -B tests/test_authenticode.py;python -I -B tests/test_tools_lock.py;python -I -B tests/test_recomplauncher_json.py;python -I -B tests/test_release.py; optionalpython -I -B tests/authenticode_standin.py --osslsigncode <tool>with osslsigncode 2.15, openssl, clang and lld-link available.77f605a2557698f8bTesting
Tested: on Linux, the final-head source suite above; 12 Authenticode unit tests, 10 tools-lock tests, 4 RecompLauncher/package tests and 18 release tests pass. The release tests emitted ResourceWarnings about their local HTTP-test sockets/cleanup, not failures. The first Authenticode test run had 5 failures and 4 errors because the authored fake thumbprint had 42 rather than 40 characters; correcting only that fixture made all 12 pass. The production guard was not relaxed.
The actual cryptographic stand-in used a throwaway self-signed RSA certificate and a tiny cross-compiled PE, not a game, builder or launcher binary. osslsigncode trusted that certificate explicitly for the test, verified all copied signatures and rejected a
.textmutation. The temporary key/files were removed; no system trust store changed. This proves mechanics, not publisher trust.Not tested: Windows PowerShell or Windows trust-store execution, a hardware/cloud provider, the timestamp service, real Forgejo jobs or release publication, the Windows builder/launcher on Windows, or SmartScreen/Defender behaviour. The authored PE was not executed. No game build/run or performance measurement; no runtime code changes. No real certificate was obtained or configured.
Needs other testers:
Screenshots
Nothing on screen. Windows prompts have not been observed.
Risk and rollback
Configured signing can stop releases if the provider, trust chain or timestamp service fails. This is intentional: no package follows failed verification. A runner label alone cannot protect a signing identity from an untrusted workflow; runner admission and provider authorization must do so before the secret is enabled.
Reverting this commit removes the optional step. Removing the certificate-selector secret returns to explicitly unsigned output; do not do that silently once a channel promises signed releases. Existing project-key release signatures are unchanged.
Reviewer notes
Read
certificate_for,sign_builder,verify_resultand the two workflow guards first. The private key stays outside the repository and command arguments. Only a certificate selector is stored as the repository secret. Windows' OS-supplied PowerShell needs no new downloadable tool pin or player tool archive.Maintainers still choose whether to buy/apply, the legal publisher, OV/EV or a signing service, custodians and release approvers, and which channels will eventually require a certificate even if configuration is missing. Sources and estimated costs are in the release docs; EV is not an immediate SmartScreen bypass. Setup resource versioning and the launcher's separate integration remain parts of issue 46. This stays WIP for the real Windows/provider/workflow proof and those decisions.
Issues
See #46
Checklist
For everyone
type(scope): description).main.python -I -B tests/run_all.pyrun and passing (any failure that also fails onmainis named above).third_party/untouched (a new folder with aPROVENANCE.mdinstead).If applicable
Assisted-by:/Co-Authored-By:trailers.tests/that reads changed source updated in this PR, not weakened.New on this forge; not opened on GitHub.
Assisted-by: GPT-6 Astra
Co-Authored-By: GPT-6 Astra [email protected]
View command line instructions
Checkout
From your project repository, check out a new branch and test the changes.