diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 217a195..a348f3f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -260,6 +260,36 @@ jobs: fi grep -q -- "--no-verify" "$RUNNER_TEMP/err" + # cosign v2 reads the release's Sigstore bundles from 2.4 on, with a flag the script adds. + - name: Install cosign v2.4 + uses: sigstore/cosign-installer@6f9f17788090df1f26f669e9d70d6ae9567deba6 # v4.1.2 + with: + cosign-release: "v2.4.1" + + - name: Install the latest release with the script and cosign v2.4 + run: | + set -eu + cosign version | grep -q 'GitVersion: *v2\.4\.1' + sh install.sh --dir "$RUNNER_TEMP/bin-v2" --no-man + "$RUNNER_TEMP/bin-v2/hodeishield" --version + + # Older cosign versions cannot read them: the script must say which version it needs. + - name: Install cosign v2.2 + uses: sigstore/cosign-installer@6f9f17788090df1f26f669e9d70d6ae9567deba6 # v4.1.2 + with: + cosign-release: "v2.2.4" + + - name: The script names the cosign version it needs + run: | + set -eu + cosign version | grep -q 'GitVersion: *v2\.2\.4' + if sh install.sh --dir "$RUNNER_TEMP/bin-old" --no-man 2> "$RUNNER_TEMP/err-old"; then + echo "::error::install.sh accepted cosign v2.2.4" + exit 1 + fi + grep -q "install cosign v3 (or v2.4 or newer)" "$RUNNER_TEMP/err-old" + test ! -e "$RUNNER_TEMP/bin-old/hodeishield" + # The repository freshness monitor must go red: its control builds small repositories with a # throwaway key, serves them from localhost and checks that every stale, altered, unsigned or # missing case fails and the current one passes. diff --git a/CHANGELOG.md b/CHANGELOG.md index dc2746d..5e4fc82 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,17 @@ commands, `--json` output and exit codes. Each release's notes start with its se ## [Unreleased] +## [0.4.1] - 2026-10-12 + +### Fixed + +- `install.sh` now checks the release's signature with cosign v2 (2.4 or newer) as well as v3. With + cosign v2 it stopped with "the Sigstore signature does not verify" although the release was + genuine: the signatures are Sigstore bundles that cosign v2 reads only with `--new-bundle-format`, + which the script now adds. With an older cosign it names the version it needs instead. The README + and `docs/verifying-releases.md` now say which cosign versions work, and what to add with v2. + ([#141](https://github.com/Hodeitek/hodeishield-cli/issues/141)) + ## [0.4.0] - 2026-10-12 ### Added @@ -212,7 +223,8 @@ commands, `--json` output and exit codes. Each release's notes start with its se - Profiles (`config`), shell completions, and release archives for Linux, macOS and Windows signed with Sigstore and with SLSA provenance. -[Unreleased]: https://github.com/Hodeitek/hodeishield-cli/compare/v0.4.0...HEAD +[Unreleased]: https://github.com/Hodeitek/hodeishield-cli/compare/v0.4.1...HEAD +[0.4.1]: https://github.com/Hodeitek/hodeishield-cli/compare/v0.4.0...v0.4.1 [0.4.0]: https://github.com/Hodeitek/hodeishield-cli/compare/v0.3.1...v0.4.0 [0.3.1]: https://github.com/Hodeitek/hodeishield-cli/compare/v0.3.0...v0.3.1 [0.3.0]: https://github.com/Hodeitek/hodeishield-cli/compare/v0.2.0...v0.3.0 diff --git a/Cargo.lock b/Cargo.lock index fb882c9..6a2ef28 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -970,7 +970,7 @@ dependencies = [ [[package]] name = "hodeishield-api" -version = "0.4.0" +version = "0.4.1" dependencies = [ "mockito", "reqwest", @@ -983,7 +983,7 @@ dependencies = [ [[package]] name = "hodeishield-cli" -version = "0.4.0" +version = "0.4.1" dependencies = [ "apple-native-keyring-store", "assert_cmd", @@ -3066,7 +3066,7 @@ checksum = "3ad82d2a33cdc9674dc7465672f271e096168fcdbe0f799d9e6db8c5892679dc" [[package]] name = "xtask" -version = "0.4.0" +version = "0.4.1" dependencies = [ "clap", "clap_mangen", diff --git a/Cargo.toml b/Cargo.toml index f27fa9c..ae1ad8e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,7 +5,7 @@ default-members = ["crates/hodeishield-api", "crates/hodeishield-cli"] [workspace.package] authors = ["Hodeitek S.L. "] -version = "0.4.0" +version = "0.4.1" edition = "2024" rust-version = "1.98" license = "Apache-2.0" diff --git a/README.md b/README.md index 7c9e9ac..cb70c54 100644 --- a/README.md +++ b/README.md @@ -56,14 +56,14 @@ section), and put `hodeishield` (`hodeishield.exe` on Windows) somewhere on your | Windows x86_64 | `hodeishield--x86_64-pc-windows-msvc.zip` | | Windows arm64 | `hodeishield--aarch64-pc-windows-msvc.zip` (from 0.4.0) | -`` has no leading `v`: release `v0.4.0` ships `hodeishield-0.4.0-…`. The Linux binaries are +`` has no leading `v`: release `v0.4.1` ships `hodeishield-0.4.1-…`. The Linux binaries are statically linked and run on any distribution. Homebrew and Scoop packages (next section) and a [container image](#container-image) are also available. From source, with the Rust toolchain installed (pin the release tag you want): ```sh -cargo install --locked --git https://github.com/Hodeitek/hodeishield-cli --tag v0.4.0 hodeishield-cli +cargo install --locked --git https://github.com/Hodeitek/hodeishield-cli --tag v0.4.1 hodeishield-cli ``` ### Homebrew and Scoop @@ -154,8 +154,10 @@ less install.sh sh install.sh --version $v ``` -It needs `cosign` to check the archive's signature and stops if it is missing; `--no-verify` skips -that check (the checksum still runs). `sh install.sh --help` lists the options (`--dir`, `--no-man`). +It needs `cosign` v3, or v2.4 or newer, to check the archive's signature, and stops if it is missing +or older; `--no-verify` skips that check (the checksum still runs). To check `install.sh` itself with +cosign v2 as above, add `--new-bundle-format` to `cosign verify-blob`. `sh install.sh --help` lists +the options (`--dir`, `--no-man`). ### Container image @@ -167,7 +169,7 @@ manager, CA certificates included) and runs as a non-root user. There is no `lat exact version, or better its digest. ```sh -v=0.4.0 # the version you want +v=0.4.1 # the version you want docker pull ghcr.io/hodeitek/hodeishield-cli:$v docker run --rm -e HODEISHIELD_API_KEY ghcr.io/hodeitek/hodeishield-cli:$v vendors list ``` @@ -180,7 +182,7 @@ The image is signed by digest with cosign (keyless) and carries the release's Cy signed attestation. Verify both before you rely on the image: ```sh -v=0.4.0 +v=0.4.1 image=ghcr.io/hodeitek/hodeishield-cli:$v cosign verify $image \ --certificate-identity https://github.com/Hodeitek/hodeishield-cli/.github/workflows/container-image.yml@refs/tags/v$v \ @@ -218,9 +220,9 @@ Each release has a `SHA256SUMS` file, a Sigstore bundle (`*.sigstore.json`) for ```sh sha256sum --ignore-missing -c SHA256SUMS -cosign verify-blob hodeishield-0.4.0-x86_64-unknown-linux-musl.tar.gz \ - --bundle hodeishield-0.4.0-x86_64-unknown-linux-musl.tar.gz.sigstore.json \ - --certificate-identity https://github.com/Hodeitek/hodeishield-cli/.github/workflows/release.yml@refs/tags/v0.4.0 \ +cosign verify-blob hodeishield-0.4.1-x86_64-unknown-linux-musl.tar.gz \ + --bundle hodeishield-0.4.1-x86_64-unknown-linux-musl.tar.gz.sigstore.json \ + --certificate-identity https://github.com/Hodeitek/hodeishield-cli/.github/workflows/release.yml@refs/tags/v0.4.1 \ --certificate-oidc-issuer https://token.actions.githubusercontent.com \ --certificate-github-workflow-trigger push ``` @@ -248,7 +250,7 @@ xattr -d com.apple.quarantine ./hodeishield # "No such xattr" means there was Or download with `curl`, which does not set that attribute in the first place: ```sh -curl -LO https://github.com/Hodeitek/hodeishield-cli/releases/download/v0.4.0/hodeishield-0.4.0-universal-apple-darwin.tar.gz +curl -LO https://github.com/Hodeitek/hodeishield-cli/releases/download/v0.4.1/hodeishield-0.4.1-universal-apple-darwin.tar.gz ``` **Windows, before 0.2.0.** Unblock the archive before unpacking it, so the executable does not diff --git a/docs/verifying-releases.md b/docs/verifying-releases.md index 87e25f0..cca6531 100644 --- a/docs/verifying-releases.md +++ b/docs/verifying-releases.md @@ -7,7 +7,7 @@ Every release of `hodeishield-cli` publishes, for each platform archive: "bundle" files produced by **keyless** `cosign sign-blob` (no private key: the workflow signs using its GitHub Actions OIDC identity, and the signature is recorded in the public Rekor transparency log). -- `hodeishield--aarch64-pc-windows-msvc.zip` (Windows on Arm, from 0.4.0), +- `hodeishield--aarch64-pc-windows-msvc.zip` (Windows on Arm, from 0.4.1), checksummed, signed with Sigstore, covered by the provenance and accompanied by its SBOM like the other archives. Its `hodeishield.exe` carries the same Authenticode signature (section 5). There is no arm64 MSI yet. @@ -25,14 +25,14 @@ Every release of `hodeishield-cli` publishes, for each platform archive: [`slsa-framework/slsa-github-generator`](https://github.com/slsa-framework/slsa-github-generator) generic generator. -The steps below use `v0.4.0` and `hodeishield-0.4.0-x86_64-unknown-linux-musl.tar.gz` +The steps below use `v0.4.1` and `hodeishield-0.4.1-x86_64-unknown-linux-musl.tar.gz` as examples — substitute the actual version and archive you downloaded. ## 1. Download the files From the release page, download: -- the archive you want (e.g. `hodeishield-0.4.0-x86_64-unknown-linux-musl.tar.gz`) +- the archive you want (e.g. `hodeishield-0.4.1-x86_64-unknown-linux-musl.tar.gz`) - `SHA256SUMS` - the matching `.sigstore.json` - `SHA256SUMS.sigstore.json` @@ -50,12 +50,14 @@ what the next two steps are for. ## 3. Verify the Sigstore signature -Requires [`cosign`](https://github.com/sigstore/cosign) (v2 or newer). +Requires [`cosign`](https://github.com/sigstore/cosign) v3. With cosign v2 (2.4 or newer), add +`--new-bundle-format` to each `cosign verify-blob` command below: the signatures are Sigstore bundles +in the format cosign v3 reads by default. Older cosign versions cannot read them. ```sh cosign verify-blob \ --bundle SHA256SUMS.sigstore.json \ - --certificate-identity https://github.com/Hodeitek/hodeishield-cli/.github/workflows/release.yml@refs/tags/v0.4.0 \ + --certificate-identity https://github.com/Hodeitek/hodeishield-cli/.github/workflows/release.yml@refs/tags/v0.4.1 \ --certificate-oidc-issuer https://token.actions.githubusercontent.com \ --certificate-github-workflow-trigger push \ SHA256SUMS @@ -65,11 +67,11 @@ Repeat for the archive itself, using its own `.sigstore.json`: ```sh cosign verify-blob \ - --bundle hodeishield-0.4.0-x86_64-unknown-linux-musl.tar.gz.sigstore.json \ - --certificate-identity https://github.com/Hodeitek/hodeishield-cli/.github/workflows/release.yml@refs/tags/v0.4.0 \ + --bundle hodeishield-0.4.1-x86_64-unknown-linux-musl.tar.gz.sigstore.json \ + --certificate-identity https://github.com/Hodeitek/hodeishield-cli/.github/workflows/release.yml@refs/tags/v0.4.1 \ --certificate-oidc-issuer https://token.actions.githubusercontent.com \ --certificate-github-workflow-trigger push \ - hodeishield-0.4.0-x86_64-unknown-linux-musl.tar.gz + hodeishield-0.4.1-x86_64-unknown-linux-musl.tar.gz ``` A successful verification confirms the file was signed by the @@ -82,11 +84,11 @@ The SBOM is verified the same way, with its own bundle: ```sh cosign verify-blob \ - --bundle hodeishield-0.4.0-x86_64-unknown-linux-musl.cdx.json.sigstore.json \ - --certificate-identity https://github.com/Hodeitek/hodeishield-cli/.github/workflows/release.yml@refs/tags/v0.4.0 \ + --bundle hodeishield-0.4.1-x86_64-unknown-linux-musl.cdx.json.sigstore.json \ + --certificate-identity https://github.com/Hodeitek/hodeishield-cli/.github/workflows/release.yml@refs/tags/v0.4.1 \ --certificate-oidc-issuer https://token.actions.githubusercontent.com \ --certificate-github-workflow-trigger push \ - hodeishield-0.4.0-x86_64-unknown-linux-musl.cdx.json + hodeishield-0.4.1-x86_64-unknown-linux-musl.cdx.json ``` ## 4. Verify SLSA build provenance @@ -95,10 +97,10 @@ Requires [`slsa-verifier`](https://github.com/slsa-framework/slsa-verifier). ```sh slsa-verifier verify-artifact \ - hodeishield-0.4.0-x86_64-unknown-linux-musl.tar.gz \ + hodeishield-0.4.1-x86_64-unknown-linux-musl.tar.gz \ --provenance-path hodeishield.intoto.jsonl \ --source-uri github.com/Hodeitek/hodeishield-cli \ - --source-tag v0.4.0 + --source-tag v0.4.1 ``` `hodeishield.intoto.jsonl` is attached to the release and covers every diff --git a/install.sh b/install.sh index 74fbfba..a0252dc 100755 --- a/install.sh +++ b/install.sh @@ -16,7 +16,8 @@ # --no-verify skip the cosign signature check (the SHA256SUMS check always runs). Only for # machines that cannot run cosign; the checksum alone does not prove who built it. # -# Needs curl, tar and sha256sum or shasum; cosign (https://docs.sigstore.dev) unless --no-verify. +# Needs curl, tar and sha256sum or shasum; cosign v3, or v2.4 or newer (https://docs.sigstore.dev), +# unless --no-verify. set -eu @@ -64,6 +65,17 @@ fi if [ "$verify" -eq 1 ] && ! command -v cosign > /dev/null 2>&1; then die "cosign is required to check the signature: install it (https://docs.sigstore.dev/cosign/system_config/installation/) and run this again. To install without the signature check, pass --no-verify." fi +# The signatures are Sigstore bundles in the format cosign v3 reads by default. cosign v2 reads them +# from 2.4 on when asked to (--new-bundle-format), and older versions cannot read them at all. +bundle_format= +if [ "$verify" -eq 1 ]; then + cosign_version=$(cosign version 2> /dev/null | awk '$1 == "GitVersion:" { print $2; exit }') + case $cosign_version in + v0.* | v1.* | v2.[0-3] | v2.[0-3].*) + die "cosign ${cosign_version} cannot read the release's signatures: install cosign v3 (or v2.4 or newer) and run this again." ;; + v2.*) bundle_format=--new-bundle-format ;; + esac +fi case "$(uname -s)" in Linux) @@ -112,7 +124,7 @@ expected=$(awk -v f="${name}.tar.gz" '$2 == f || $2 == "*" f { print $1 }' "${tm printf 'Checksum: OK\n' if [ "$verify" -eq 1 ]; then - cosign verify-blob \ + cosign verify-blob ${bundle_format:+"$bundle_format"} \ --bundle "${tmp}/${name}.tar.gz.sigstore.json" \ --certificate-identity "${WORKFLOW}@refs/tags/v${version}" \ --certificate-oidc-issuer "$ISSUER" \