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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions .github/workflows/feed.yml
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,16 @@ jobs:
every installed copy carries. It answers, and it cannot be installed: $url"
return 1
fi
local bound=0
bash scripts/signed_for.sh one.sig "$expected" || bound=$?
case $bound in
0) ;;
2) echo "::warning::the installer for $os in $what is signed for no version; \
copies that require one will not take it" ;;
*) echo "::error::the installer for $os in $what is signed for another version \
than the «${expected}» it is offered as: $url"
return 1 ;;
esac
echo " $os verifies"
done
}
Expand Down
26 changes: 23 additions & 3 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -445,8 +445,13 @@ jobs:
v="${{ needs.version.outputs.version }}"
exe="../dist/copypaste-installer-$v-windows-x86_64.exe"
rm -f "$exe.sig"
npm run tauri -- signer sign "$exe"
npm run tauri -- signer sign --app-version "$v" "$exe"
test -s "$exe.sig" || { echo "::error::no updater signature was written"; exit 1; }
base64 -d < "$exe.sig" > "$RUNNER_TEMP/one.minisig"
if ! bash ../scripts/signed_for.sh "$RUNNER_TEMP/one.minisig" "$v"; then
echo "::error::the updater signature is not bound to $v"
exit 1
fi

- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
with:
Expand Down Expand Up @@ -637,9 +642,15 @@ jobs:
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }}
TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }}
run: |
v="${{ needs.version.outputs.version }}"
out="../target/$TARGET/release/bundle/macos"
npm run tauri -- signer sign "$out/CopyPaste.app.tar.gz"
npm run tauri -- signer sign --app-version "$v" "$out/CopyPaste.app.tar.gz"
test -s "$out/CopyPaste.app.tar.gz.sig"
base64 -d < "$out/CopyPaste.app.tar.gz.sig" > "$RUNNER_TEMP/one.minisig"
if ! bash ../scripts/signed_for.sh "$RUNNER_TEMP/one.minisig" "$v"; then
echo "::error::the updater signature is not bound to $v"
exit 1
fi

- name: Name it after the release
run: |
Expand Down Expand Up @@ -886,9 +897,14 @@ jobs:
could ever verify this update. Nothing was published."
exit 1
fi
if ! bash scripts/signed_for.sh one.minisig "$v"; then
echo "::error::$one is not signed for $v, and every copy refuses an update whose \
signature does not name it. Nothing was published."
exit 1
fi
done
rm -f feed.pub one.minisig
echo "the signatures and the shipped key are halves of the same pair"
echo "the signatures and the shipped key are halves of the same pair, bound to $v"

- name: The manifest a 2.x can read
if: vars.BRIDGE_MANIFEST == 'true'
Expand Down Expand Up @@ -1305,6 +1321,10 @@ jobs:
echo "::error::$os does not verify against the key this build ships"
exit 1
fi
if ! bash scripts/signed_for.sh one.sig "$v"; then
echo "::error::$os is not signed for $v, and every copy from here on refuses it"
exit 1
fi
done

msstore:
Expand Down
13 changes: 9 additions & 4 deletions RELEASING.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,11 +70,16 @@ never offered anything; one under `Caskroom/copypaste-beta` or
`Caskroom/copypaste` is told its own `brew` command rather than handed an
installer; anything else installs its own update.

Three things guard the install, all of them borrowed from Tisty: the download
Four things guard the install, all of them borrowed from Tisty: the download
address must be this repository's releases on `github.com` (or
`objects.githubusercontent.com`) before a byte is fetched, the version is pinned to the one the person was shown so a
feed that moves cannot hand over another, and a copy running from the mounted
`.dmg` refuses rather than failing after the whole download. The panel is
`objects.githubusercontent.com`) before a byte is fetched; the version is
pinned to the one the person was shown, so a feed that moves cannot hand over
another; every updater signature is bound to its version (`signer sign
--app-version`, `requireSignedVersion`), so an older release cannot be served
under a newer number, and a release whose signatures do not say it stops
before anything is published (`scripts/signed_for.sh`, run where it is signed,
before `publish`, in `verify` and every morning in `feed.yml`); and a copy running from the mounted `.dmg` refuses rather than failing
after the whole download. The panel is
stopped first, because on Windows an installer cannot replace a binary that is
running, and it is brought back if the install does not go through.

Expand Down
10 changes: 10 additions & 0 deletions app/src-tauri/src/update_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -275,3 +275,13 @@ fn a_candidate_downloads_from_the_candidates_first_and_a_stable_only_from_the_st
assert!(one.starts_with("https://raw.githubusercontent.com/rgdevment/CopyPaste/manifest/"));
}
}

#[test]
fn a_copy_refuses_an_update_not_signed_for_its_version() {
let conf: serde_json::Value =
serde_json::from_str(include_str!("../tauri.conf.json")).expect("tauri.conf.json reads");
assert_eq!(
conf["plugins"]["updater"]["requireSignedVersion"],
serde_json::Value::Bool(true)
);
}
1 change: 1 addition & 0 deletions app/src-tauri/tauri.conf.json
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@
"endpoints": [
"https://raw.githubusercontent.com/rgdevment/CopyPaste/manifest/latest.json"
],
"requireSignedVersion": true,
"windows": {
"installMode": "passive"
}
Expand Down
18 changes: 18 additions & 0 deletions scripts/signed_for.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
#!/usr/bin/env bash
set -uo pipefail

sig=${1:?the decoded minisign signature}
version=${2:?the version it has to be signed for}

comment=$(grep -m1 '^trusted comment: ' "$sig" | tr -d '\r')
fields=$(printf '%s\n' "${comment#trusted comment: }" | tr '\t' '\n')

if printf '%s\n' "$fields" | grep -qxF "version:$version"; then
exit 0
fi
if printf '%s\n' "$fields" | grep -q '^version:'; then
echo "signed for $(printf '%s\n' "$fields" | sed -n 's/^version://p' | head -1), not $version" >&2
exit 1
fi
echo "signed for no version at all" >&2
exit 2
Loading