Skip to content

Commit 6533555

Browse files
josiahsrcclaude
andcommitted
Fix Windows updater signature verification failures
Azure Trusted Signing rewrites the NSIS installer after Tauri generates its minisign updater signature, so every NSIS auto-update has failed with "signature verification failed" (confirmed invalid on 0.0.641, 0.0.644, and 0.0.650; MSI/macOS/Linux artifacts verify fine). Add a resign-windows-updater workflow that re-signs the final exe bytes, patches latest.json, and verifies every signature in the manifest before publishing. It runs in the release pipeline before update-channel and is manually dispatchable to repair already-published releases. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 parent 8a2bce2 commit 6533555

2 files changed

Lines changed: 109 additions & 1 deletion

File tree

‎.github/workflows/_release-desktop-impl.yml‎

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -872,9 +872,20 @@ jobs:
872872
Write-Host "Uploading $destName to release $env:RELEASE_TAG"
873873
gh release upload $env:RELEASE_TAG $destName --clobber
874874
875+
# Azure Trusted Signing (above) rewrites the NSIS exe after Tauri computed
876+
# its updater signature, so the sig and latest.json must be regenerated from
877+
# the final bytes before the channel manifest is published.
878+
resign-windows-updater:
879+
name: Re-sign Windows updater
880+
needs: [metadata, build]
881+
uses: ./.github/workflows/resign-windows-updater.yml
882+
with:
883+
release_tag: ${{ needs.metadata.outputs.tag_name }}
884+
secrets: inherit
885+
875886
update-channel:
876887
name: Update channel release
877-
needs: [metadata, build]
888+
needs: [metadata, build, resign-windows-updater]
878889
runs-on: ubuntu-latest
879890
permissions:
880891
contents: write
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
name: Re-sign Windows updater
2+
3+
# Azure Trusted Signing modifies the NSIS installer *after* Tauri generates its
4+
# minisign updater signature, which invalidates it and breaks auto-updates with
5+
# "signature verification failed". This workflow re-signs the final exe bytes,
6+
# patches latest.json, and verifies every signature in the manifest before
7+
# publishing. It runs as part of the desktop release pipeline and can be
8+
# dispatched manually to repair an already-published release.
9+
10+
on:
11+
workflow_call:
12+
inputs:
13+
release_tag:
14+
description: "Release tag holding the artifacts (e.g. desktop-v0.0.650)"
15+
required: true
16+
type: string
17+
channel_tag:
18+
description: "Channel tag that should also receive the patched latest.json (e.g. desktop-prod)"
19+
required: false
20+
type: string
21+
default: ""
22+
workflow_dispatch:
23+
inputs:
24+
release_tag:
25+
description: "Release tag holding the artifacts (e.g. desktop-v0.0.650)"
26+
required: true
27+
type: string
28+
channel_tag:
29+
description: "Channel tag that should also receive the patched latest.json (e.g. desktop-prod)"
30+
required: false
31+
type: string
32+
default: ""
33+
34+
jobs:
35+
resign:
36+
name: Re-sign and verify updater signatures
37+
runs-on: ubuntu-latest
38+
permissions:
39+
contents: write
40+
env:
41+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
42+
GH_REPO: ${{ github.repository }}
43+
RELEASE_TAG: ${{ inputs.release_tag }}
44+
CHANNEL_TAG: ${{ inputs.channel_tag }}
45+
steps:
46+
- name: Download latest.json and updater artifacts
47+
run: |
48+
set -euo pipefail
49+
mkdir -p dist
50+
gh release download "$RELEASE_TAG" --pattern "latest.json" --dir dist --clobber
51+
jq -r '.platforms[].url | sub(".*/"; "")' dist/latest.json | sort -u | while read -r asset; do
52+
gh release download "$RELEASE_TAG" --pattern "$asset" --dir dist --clobber
53+
done
54+
55+
- name: Re-sign NSIS installers
56+
env:
57+
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_PRIVATE_KEY }}
58+
TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_PRIVATE_KEY_PASSWORD }}
59+
run: |
60+
set -euo pipefail
61+
shopt -s nullglob
62+
for exe in dist/*-setup.exe; do
63+
npx --yes @tauri-apps/cli@2 signer sign "$exe"
64+
sig="$(cat "${exe}.sig")"
65+
name="$(basename "$exe")"
66+
jq --arg name "$name" --arg sig "$sig" \
67+
'.platforms |= with_entries(if (.value.url | endswith("/" + $name)) then .value.signature = $sig else . end)' \
68+
dist/latest.json > dist/latest.json.tmp
69+
mv dist/latest.json.tmp dist/latest.json
70+
done
71+
72+
- name: Verify every signature in latest.json
73+
env:
74+
TAURI_UPDATER_PUBLIC_KEY: ${{ secrets.TAURI_UPDATER_PUBLIC_KEY }}
75+
run: |
76+
set -euo pipefail
77+
sudo apt-get update -qq >/dev/null
78+
sudo apt-get install -y -qq minisign >/dev/null
79+
pub="$( (base64 -d <<<"$TAURI_UPDATER_PUBLIC_KEY" 2>/dev/null || printf '%s' "$TAURI_UPDATER_PUBLIC_KEY") | grep -m1 '^RW' )"
80+
jq -r '.platforms | to_entries[] | [.key, (.value.url | sub(".*/"; "")), .value.signature] | @tsv' dist/latest.json \
81+
| while IFS=$'\t' read -r platform asset sig; do
82+
printf '%s' "$sig" | base64 -d > asset.minisig
83+
minisign -Vm "dist/$asset" -x asset.minisig -P "$pub" >/dev/null
84+
echo "verified: $platform ($asset)"
85+
done
86+
87+
- name: Publish re-signed artifacts
88+
run: |
89+
set -euo pipefail
90+
shopt -s nullglob
91+
for sig in dist/*-setup.exe.sig; do
92+
gh release upload "$RELEASE_TAG" "$sig" --clobber
93+
done
94+
gh release upload "$RELEASE_TAG" dist/latest.json --clobber
95+
if [[ -n "$CHANNEL_TAG" ]]; then
96+
gh release upload "$CHANNEL_TAG" dist/latest.json --clobber
97+
fi

0 commit comments

Comments
 (0)