Skip to content

Add code signing and download optimization steps for Windows and macOS build artifacts - #16

Merged
Iamliuxiaozhen merged 10 commits into
mainfrom
Signature-app
Nov 8, 2026
Merged

Iamliuxiaozhen merged 10 commits into
mainfrom
Signature-app

Conversation

@Iamliuxiaozhen

Copy link
Copy Markdown
Member

Introduce optional signing support in the reusable build workflow, driven by the SIGNATURE_KEYS secret (an openssl PEM containing private key and certificate) and an optional SIGNATURE_KEYS_PASSWORD.

  • Document the signing behavior and secrets at the top of the workflow
  • Expose HAS_SIGNING_KEYS so signing steps are skipped when unset
  • Windows: build the client first, convert the PEM to a PFX on the runner, derive the certificate subject for the MSIX Publisher, and sign all client executables with signtool before packaging exe / zip / msi / msix
  • macOS: convert the PEM to a P12 and sign the app and dmg before upload

Unsigned builds remain the default when no keys are provided.

Introduce optional signing support in the reusable build workflow, driven
by the SIGNATURE_KEYS secret (an openssl PEM containing private key and
certificate) and an optional SIGNATURE_KEYS_PASSWORD.

- Document the signing behavior and secrets at the top of the workflow
- Expose HAS_SIGNING_KEYS so signing steps are skipped when unset
- Windows: build the client first, convert the PEM to a PFX on the runner,
  derive the certificate subject for the MSIX Publisher, and sign all
  client executables with signtool before packaging exe / zip / msi / msix
- macOS: convert the PEM to a P12 and sign the app and dmg before upload

Unsigned builds remain the default when no keys are provided.
Read the private key from secrets.SIGNATURE_KEYS and the certificate
from the new vars.SIGNATURE_CRT, writing each to its own temp file
and combining them into PFX/P12 at build time instead of expecting a
single combined PEM. Fail early with clear errors when the certificate
variable is missing or lacks a BEGIN CERTIFICATE block, so that the
certificate can be maintained as a visible variable while the key
stays secret.
- Retry delays now increase exponentially (1x, 2x, 4x...) to avoid
  hammering the same source on repeated failures
- electron.d.ts download tries GitHub first, then npmmirror mirrors when
  GitHub Releases returns errors like 504
- Each mirror download is verified against the expected SHA-256 checksum,
  with retries, longer timeout, and clear error messages on failure
@Iamliuxiaozhen Iamliuxiaozhen changed the title ci(build): add code signing for Windows and macOS artifacts Add code signing and download optimization steps for Windows and macOS build artifacts Nov 8, 2026
The Windows SDK `bin` directory also contains non-version folders such
as `x86` and `x64`. Sorting all directories by name descending could
select one of these instead of a real version (e.g. `x86` > `10.x`),
producing invalid paths to `signtool.exe` and failing signing steps.

Filter to semver-like directory names, sort them as `[version]`
objects, and verify the computed `signtool.exe` path exists before
using it. This makes signing and installer PATH setup robust.
@Iamliuxiaozhen

Copy link
Copy Markdown
Member Author

Oh No

1 identity imported.
keychain: "/Users/runner/work/_temp/buildagent.keychain"
version: 256
class: 0x00000010 
attributes:
    0x00000000 <uint32>=0x00000010 
    0x00000001 <blob>="BlockIDE"
    0x00000002 <blob>=<NULL>
    0x00000003 <uint32>=0x00000001 
    0x00000004 <uint32>=0x00000000 
    0x00000005 <uint32>=0x00000000 
    0x00000006 <blob>=0x89E2A55324D35C40D8819E5B23D7B7237833FFD8  "\211\342\245S$\323\134@\330\201\236[#\327\267#x3\377\330"
    0x00000007 <blob>=<NULL>
    0x00000008 <blob>=0x7B38373139316361322D306663392D313164342D383439612D3030303530326235323132327D00  "{87191ca2-0fc9-11d4-849a-000502b52122}\000"
    0x00000009 <uint32>=0x0000002A  "\000\000\000*"
    0x0000000A <uint32>=0x00000C00 
    0x0000000B <uint32>=0x00000C00 
    0x0000000C <blob>=0x0000000000000000 
    0x0000000D <blob>=0x0000000000000000 
    0x0000000E <uint32>=0x00000001 
    0x0000000F <uint32>=0x00000001 
    0x00000010 <uint32>=0x00000001 
    0x00000011 <uint32>=0x00000000 
    0x00000012 <uint32>=0x00000001 
    0x00000013 <uint32>=0x00000001 
    0x00000014 <uint32>=0x00000001 
    0x00000015 <uint32>=0x00000001 
    0x00000016 <uint32>=0x00000001 
    0x00000017 <uint32>=0x00000001 
    0x00000018 <uint32>=0x00000001 
    0x00000019 <uint32>=0x00000001 
    0x0000001A <uint32>=0x00000001 
     0 valid identities found
No code signing identity found in the temporary keychain
Error: Process completed with exit code 1.

Register the generated self-signed certificate as a system trust root
so that `security find-identity -v` reports it as valid and codesign
can build a complete certificate chain. Without this, the identity is
filtered out as untrusted and the macOS build fails to sign.
- Add Electron mirror to CI to work around intermittent GitHub 504s
- Skip osx-sign identity validation for self-signed certificates
- Fix typo in test-agent-host-e2e script path
macOS codesign failed to find a signing identity when SIGNATURE_CRT
contained a leaf certificate issued by a CA missing from the trust
store, because the chain could not be built (CSSMERR_TP_NOT_TRUSTED).

- Split the PEM bundle and export only the leaf cert into the PKCS#12,
  importing any intermediate/root certs into the temporary keychain
- Check for an expired leaf certificate and fail early
- Use trustAsRoot for CA-issued leaves (trustRoot only works for
  self-signed certs) and restrict trust to the codeSign policy
- Select the identity by SHA-1 fingerprint instead of display name,
  which can carry diagnostic suffixes like CSSMERR_TP_NOT_TRUSTED
- Warn when the identity is still not trusted
codesign must build the chain to a self-signed root itself, so a bare
leaf certificate in SIGNATURE_CRT fails with errSecInternalComponent.
Validate the chain (issuer/subject matching, self-signed root) and fail
early with actionable guidance, then import all CA certs into the
temporary keychain instead of pinning the leaf as trust anchor.
When SIGNATURE_CRT contains only a CA-issued leaf certificate,
codesign fails with errSecInternalComponent because it cannot build
a chain to a self-signed root. Instead of failing the build, generate
a self-signed certificate from the same private key (preserving the
leaf's subject) and use it for signing. A warning reminds users to
append the issuing CA to SIGNATURE_CRT for a stable chain.

Also disable codesign timestamping unless the chain is anchored at an
Apple root, since Apple's timestamp service does not support
non-Apple signing identities and would otherwise fail the signing step.
@Iamliuxiaozhen
Iamliuxiaozhen marked this pull request as ready for review November 8, 2026 17:14
@Iamliuxiaozhen

Copy link
Copy Markdown
Member Author

It’s good to go, but I’ll refine it a bit further.

Accept the signing certificate chain from `secrets.CRT` or `vars.CRT`
(full chain) with fallback to the legacy `vars.SIGNATURE_CRT`, and
normalize CRLF line endings before parsing PEM.

Detect the leaf certificate by matching its public key against
`SIGNATURE_KEYS`, then reorder the chain leaf-first so certificate
order in the PEM no longer matters. Fail with clear errors when no
certificate block is found or no certificate matches the key.
@Iamliuxiaozhen
Iamliuxiaozhen merged commit 9e96a7e into main Nov 8, 2026
58 checks passed
@Iamliuxiaozhen
Iamliuxiaozhen deleted the Signature-app branch November 8, 2026 22:49
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant