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
126 changes: 126 additions & 0 deletions .github/workflows/publish-release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,15 @@
# release as a pacman repository (needs secrets
# GPG_SIGNING_KEY and GPG_PASSPHRASE). Independent
# of the AUR, which has had long outages.
# * Signatures + provenance -> once every asset is attached, each one
# without a detached signature gets one from the
# same key, and a SLSA provenance statement
# listing every asset by SHA-256 is signed through
# Sigstore and attached as
# release-assets.intoto.jsonl. A manual dispatch
# with provenance-only set skips every channel and
# only exercises the provenance jobs against an
# existing release, uploading nothing.
#
# Required repository secrets (Settings -> Secrets and variables -> Actions):
# COPR_CONFIG the contents of your ~/.config/copr
Expand Down Expand Up @@ -48,6 +57,10 @@
tag:
description: "Existing release tag to (re)publish, e.g. v0.12.1"
required: true
provenance-only:
description: "Only hash the existing assets and build provenance, without uploading it"
type: boolean
default: false

# Read-only token by default; the jobs that attach assets to the release
# ask for contents: write themselves, and the channel pushes use their own
Expand Down Expand Up @@ -79,6 +92,7 @@
debian:
name: Debian/Ubuntu .deb
needs: validate
if: ${{ !inputs.provenance-only }}
runs-on: ubuntu-latest
# Only this job needs write access, to attach the .deb to the release.
permissions:
Expand Down Expand Up @@ -129,6 +143,7 @@
sbom:
name: SBOM (CycloneDX)
needs: validate
if: ${{ !inputs.provenance-only }}
runs-on: ubuntu-latest
permissions:
contents: write
Expand Down Expand Up @@ -156,6 +171,7 @@
telemetry-helpers:
name: Telemetry helpers (relay + SCS plugin)
needs: validate
if: ${{ !inputs.provenance-only }}
runs-on: ubuntu-latest
# Attaches the built helpers to the release.
permissions:
Expand Down Expand Up @@ -210,6 +226,7 @@
copr:
name: Fedora COPR (akmod)
needs: validate
if: ${{ !inputs.provenance-only }}
runs-on: ubuntu-latest
container: fedora:latest
steps:
Expand Down Expand Up @@ -258,6 +275,7 @@
obs:
name: openSUSE OBS
needs: validate
if: ${{ !inputs.provenance-only }}
runs-on: ubuntu-latest
container: opensuse/tumbleweed:latest
steps:
Expand Down Expand Up @@ -358,6 +376,7 @@
aur-build:
name: Arch AUR (build check)
needs: validate
if: ${{ !inputs.provenance-only }}
runs-on: ubuntu-latest
container:
image: archlinux:base-devel
Expand Down Expand Up @@ -586,3 +605,110 @@
logitech-trueforce-signing-key.asc \
--clobber

# Everything below runs once every job that attaches assets has finished,
# so that the signatures and the provenance cover the release as shipped.
# On a provenance-only dispatch the asset jobs are skipped, which is fine;
# anything else short of success means the release is incomplete and gets
# no signatures and no provenance until it is republished.
sign-assets:
name: Sign the remaining assets
needs: [validate, debian, sbom, telemetry-helpers, archrepo]
if: ${{ !cancelled() && needs.validate.result == 'success' && !contains(needs.*.result, 'failure') && !contains(needs.*.result, 'cancelled') && !inputs.provenance-only }}
permissions:
contents: write
runs-on: ubuntu-latest
steps:
- name: Download every asset of the release
env:
GH_TOKEN: ${{ github.token }}
run: |
set -eu
gh release download "$TAG" --repo "$GITHUB_REPOSITORY" --dir assets
ls -1 assets
- name: Sign every asset that has no signature yet
env:
GPG_SIGNING_KEY: ${{ secrets.GPG_SIGNING_KEY }}
GPG_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }}
GH_TOKEN: ${{ github.token }}
run: |
set -eu
if [ -z "${GPG_SIGNING_KEY:-}" ]; then
echo "::error::GPG_SIGNING_KEY is not set; see README 'Arch Linux repository'." >&2
exit 1
fi
# Same scratch keyring and loopback pinentry as the Arch repo job.
export GNUPGHOME
GNUPGHOME=$(mktemp -d)
chmod 700 "$GNUPGHOME"
printf '%s\n' "$GPG_SIGNING_KEY" | gpg --batch --import
KEYID=$(gpg --list-secret-keys --with-colons | awk -F: '/^sec:/ {print $5; exit}')
echo "signing with $KEYID"
cd assets
new=""
for f in *; do
# Signatures, the public key and provenance sign nothing.
case "$f" in *.sig|*.asc|*.intoto.jsonl) continue;; esac
[ -e "$f.sig" ] && continue
gpg --batch --yes --pinentry-mode loopback \
--passphrase "$GPG_PASSPHRASE" \
--detach-sign --no-armor -u "$KEYID" "$f"
gpg --verify "$f.sig" "$f"
new="$new $f.sig"
done
if [ -n "$new" ]; then
# shellcheck disable=SC2086
gh release upload "$TAG" --repo "$GITHUB_REPOSITORY" $new --clobber
fi
echo "signed:${new:- nothing, every asset already had a signature}"

# A SLSA build provenance statement listing every asset of the release by
# SHA-256, signed keylessly through Sigstore by the SLSA generic generator
# and attached as release-assets.intoto.jsonl. Anyone can then check that
# an asset they downloaded is the one this workflow published:
# slsa-verifier verify-artifact <asset> \
# --provenance-path release-assets.intoto.jsonl \
# --source-uri github.com/mescon/logitech-trueforce-linux-driver \
# --source-tag vX.Y.Z
hashes:
name: Hash the release assets
needs: [validate, sign-assets]
if: ${{ !cancelled() && needs.validate.result == 'success' && needs.sign-assets.result != 'failure' && needs.sign-assets.result != 'cancelled' }}
permissions:
contents: read
runs-on: ubuntu-latest
outputs:
hashes: ${{ steps.hash.outputs.hashes }}
steps:
- name: Download every asset of the release
env:
GH_TOKEN: ${{ github.token }}
run: |
set -eu
gh release download "$TAG" --repo "$GITHUB_REPOSITORY" --dir assets
# A republish must not list the previous statement as a subject.
rm -f assets/*.intoto.jsonl
ls -1 assets
- name: List them as sha256sum output, base64-encoded
id: hash
run: |
set -eu
cd assets
sha256sum -- *
echo "hashes=$(sha256sum -- * | base64 -w0)" >> "$GITHUB_OUTPUT"

provenance:
name: Sign the provenance
needs: hashes
permissions:
actions: read # the generator reads this run's metadata
id-token: write # keyless signing through Sigstore
contents: write # attaches the statement to the release
# Referenced by tag, not by commit hash, on purpose: slsa-verifier can
# only verify the generator's ref when it is a tag (the generator's
# README, "Referencing SLSA builders and generators").
uses: slsa-framework/slsa-github-generator/.github/workflows/generator_generic_slsa3.yml@v2.1.0

Check failure on line 709 in .github/workflows/publish-release.yml

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Use full commit SHA hash for this dependency.

See more on https://sonarcloud.io/project/issues?id=mescon_logitech-rs50-linux-driver&issues=AaChuvhKF8Hb06DmT9v0&open=AaChuvhKF8Hb06DmT9v0&pullRequest=101
with:
base64-subjects: ${{ needs.hashes.outputs.hashes }}
provenance-name: release-assets.intoto.jsonl
upload-assets: ${{ !inputs.provenance-only }}
upload-tag-name: ${{ github.event.release.tag_name || inputs.tag }}
14 changes: 14 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,20 @@ the contract is "it works on RS50 and G Pro as listed here".

## Unreleased

**Every release asset is signed, and the release carries provenance.** The
signing job only ever covered the Arch packages and their repository
database, although the documentation said every asset; the Debian
packages, the telemetry helpers and the SBOMs went out unsigned. The
release workflow now ends by signing every asset that has no detached
signature yet, with the same key, and by attaching a SLSA build provenance
statement, `release-assets.intoto.jsonl`, that lists every asset by SHA-256
and is signed through Sigstore by the SLSA generic generator.
`slsa-verifier` can then confirm that an asset is the one this repository's
workflow published at that tag; the README shows how. The workflow accepts
a `provenance-only` dispatch that exercises the provenance jobs against an
existing release without uploading anything. The README and the assurance
document now say what was signed up to 0.41.0 instead of overstating it.

**Reviewed dependency advisories are recorded where the scanners look.** The
four RustSec notices against the GUI toolkit's font and image stack (bincode,
paste, rustybuzz, ttf-parser, all "crate is unmaintained", none a
Expand Down
35 changes: 25 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -717,24 +717,39 @@ with the problems we know about and have not fixed.

## Verifying a release

Every asset on a [release](https://github.com/mescon/logitech-trueforce-linux-driver/releases)
is signed with the project's GnuPG key, fingerprint
Assets on a [release](https://github.com/mescon/logitech-trueforce-linux-driver/releases)
are signed with the project's GnuPG key, fingerprint
`4B5B DD78 0272 3B28 9FA9 34CA CD77 C00A 443B 9E79`, whose public half is
attached to each release as `logitech-trueforce-signing-key.asc`. The Arch
repository above is verified by pacman once the key is trusted. For any
other asset:
attached to each release as `logitech-trueforce-signing-key.asc`. Up to
0.41.0 that covers the Arch packages and the repository database, which
pacman verifies once the key is trusted; from 0.42.0 every asset has a
detached `.sig`. To check one by hand:

```bash
gpg --import logitech-trueforce-signing-key.asc
gpg --fingerprint 4B5BDD7802723B289FA934CACD77C00A443B9E79 # must match the line above
gpg --verify logi-wheel_0.41.0-1_amd64.deb.sig logi-wheel_0.41.0-1_amd64.deb
gpg --verify logi-wheel-0.41.0-1-x86_64.pkg.tar.zst.sig logi-wheel-0.41.0-1-x86_64.pkg.tar.zst
```

A release is from this project when it verifies against that key; a change
of key would be announced here and in the release notes. From 0.42.0 each
release also carries a CycloneDX SBOM per binary. Only the latest release
is supported; see [SECURITY.md](SECURITY.md) for the policy and the
reporting process.
of key would be announced here and in the release notes.

From 0.42.0 each release also carries a CycloneDX SBOM per binary and a
SLSA provenance statement, `release-assets.intoto.jsonl`, listing every
asset by SHA-256 and signed through Sigstore by the release workflow itself.
[slsa-verifier](https://github.com/slsa-framework/slsa-verifier) checks that
an asset is the one the workflow published, from this repository, at that
tag:

```bash
slsa-verifier verify-artifact logi-wheel_0.42.0-1_amd64.deb \
--provenance-path release-assets.intoto.jsonl \
--source-uri github.com/mescon/logitech-trueforce-linux-driver \
--source-tag v0.42.0
```

Only the latest release is supported; see [SECURITY.md](SECURITY.md) for
the policy and the reporting process.

## Project documents

Expand Down
25 changes: 17 additions & 8 deletions docs/SECURITY_ASSURANCE.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ Who could attack, through what, and what the consequence would be.
| **A Wine process** writing force-feedback reports to the virtual wheel | The PID report decoder in logi-ffb | A proxy crash (loss of force) or wrong forces | The decoder validates report ids, lengths and block indexes and is fuzzed; effects are translated into the kernel's own force-feedback interface, which bounds them; the proxy runs as the user, with no more access than the game itself has. |
| **A game's prefix** where the Windows helpers run | The escape proxy and relay, and through them the wheel's sysfs range | Wrong rotation range; a stalled game | The helpers only read the game's shared memory and write telemetry to loopback; the one write they perform on the wheel (the rotation range the SDK announces) is bounds-checked to the range a wheel accepts, and can be switched off. |
| **Another local user** | The wheel's sysfs attributes, which the udev rule makes world-writable | Changed settings; a changed rotation range while someone is driving | Accepted risk, stated here: the attributes are world-writable so that settings apps and games work without root, and a machine with a racing wheel attached is assumed to be single-user. No attribute grants any privilege beyond the wheel itself. |
| **A dependency or a build tool** | Everything | Compromised releases | Dependencies are pinned by `Cargo.lock`, updated by Dependabot, checked by `cargo audit` on every push and weekly; GitHub Actions are pinned to commit hashes; workflow tokens are read-only by default; releases are signed and the signing key's fingerprint is published. |
| **A dependency or a build tool** | Everything | Compromised releases | Dependencies are pinned by `Cargo.lock`, updated by Dependabot, checked by `cargo audit` on every push and weekly; GitHub Actions are pinned to commit hashes; workflow tokens are read-only by default; releases are signed, carry SLSA provenance for every asset, and the signing key's fingerprint is published. |
| **The maintainer's accounts and secrets** | Releases and channels | Compromised releases | Two-factor authentication is required for write access; secrets live only in GitHub Actions and are rotated on any suspicion; the rules are in GOVERNANCE.md. |

The critical code paths, in order of consequence, are the kernel module's
Expand Down Expand Up @@ -107,14 +107,23 @@ module source itself, which the packages ship.

## Verifying a release

Every release asset is signed with the project's GnuPG key, fingerprint
Release assets are signed with the project's GnuPG key, fingerprint
`4B5B DD78 0272 3B28 9FA9 34CA CD77 C00A 443B 9E79`, published with each
release as `logitech-trueforce-signing-key.asc`. The README's install
section says how to verify: pacman does it once the key is trusted; for
any other asset, `gpg --verify <asset>.sig <asset>` after importing the
key and checking the fingerprint. The identity behind a release is that
key: a release signed by any other key is not from this project until the
README says the key has changed.
release as `logitech-trueforce-signing-key.asc`: the Arch packages and
repository database up to 0.41.0, every asset from 0.42.0. The README's
"Verifying a release" says how to check one: pacman does it once the key
is trusted; for any other asset, `gpg --verify <asset>.sig <asset>` after
importing the key and checking the fingerprint. The identity behind a
release is that key: a release signed by any other key is not from this
project until the README says the key has changed.

From 0.42.0 each release also carries a SLSA build provenance statement
(`release-assets.intoto.jsonl`) produced by the SLSA generic generator:
the release workflow hashes every attached asset and the generator signs
the list keylessly through Sigstore, recording the repository, the tag and
the workflow that published them. `slsa-verifier` checks an asset against
it, as the README shows. The GnuPG signature says who signed; the
provenance says what built it and from which commit.

## Support and end of support

Expand Down
Loading