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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -28,3 +28,4 @@ go.work.sum
.DS_Store

dist/
tmp/
20 changes: 19 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,23 @@ Date format: `YYYY-MM-DD`
### Fixed
### Security

---

## [1.11.0] - 2025-11-20

### Added
- **risk:** Added `signature-verify` make target to verify latest release's digital signatures for the current GOOS and GOARCH combination.

### Changed
- **debt:** Upgraded dependencies to their latest stable versions.

### Deprecated
### Removed
### Fixed
- **defect:** Fixed `README.md` instructions for verifying module checksums.

### Security

---
## [1.10.3] - 2025-11-07

Expand Down Expand Up @@ -180,7 +197,8 @@ Date format: `YYYY-MM-DD`
### Fixed
### Security

[Unreleased]: https://github.com/sixafter/semver/compare/v1.10.3...HEAD
[Unreleased]: https://github.com/sixafter/semver/compare/v1.11.0...HEAD
[1.11.0]: https://github.com/sixafter/semver/compare/v1.10.3...v1.11.0
[1.10.3]: https://github.com/sixafter/semver/compare/v1.10.1...v1.10.3
[1.10.1]: https://github.com/sixafter/semver/compare/v1.10.0...v1.10.1
[1.10.0]: https://github.com/sixafter/semver/compare/v1.9.0...v1.10.0
Expand Down
11 changes: 9 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -81,8 +81,15 @@ vuln: ## Check for vulnerabilities

.PHONY: release-verify
release-verify: ## Verify the release
rm -fr dist
goreleaser --config .goreleaser.yaml release --snapshot
@scripts/verify-release.sh

.PHONY: module-verify
mod-verify: ## Verify Go module integrity
@scripts/verify-mod.sh

.PHONY: signature-verify
signature-verify: ## Verify latest release's digital signatures
@scripts/verify-sig.sh

.PHONY: help
help: ## Display this help screen
Expand Down
42 changes: 30 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,27 +33,45 @@ A Semantic Versioning 2.0.0 compliant parser and utility library written in Go.
To verify the integrity of the release, you can use Cosign to check the signature and checksums. Follow these steps:

```sh
# Fetch the latest release tag from GitHub API (e.g., "v1.8.0")
# Fetch the latest release tag from GitHub API (e.g., "v1.11.0")
TAG=$(curl -s https://api.github.com/repos/sixafter/semver/releases/latest | jq -r .tag_name)

# Remove leading "v" for filenames (e.g., "v1.8.0" -> "1.8.0")
# Remove leading "v" for filenames (e.g., "v1.11.0" -> "1.11.0")
VERSION=${TAG#v}

# Verify the release tarball
# ---------------------------------------------------------------------
# Verify the source tarball using Sigstore bundle
# ---------------------------------------------------------------------

# Download the release tarball and its Sigstore bundle
curl -LO "https://github.com/sixafter/semver/releases/download/${TAG}/semver-${VERSION}.tar.gz"
curl -LO "https://github.com/sixafter/semver/releases/download/${TAG}/semver-${VERSION}.tar.gz.sigstore.json"

# Verify the tarball with Cosign
cosign verify-blob \
--key https://raw.githubusercontent.com/sixafter/semver/main/cosign.pub \
--signature semver-${VERSION}.tar.gz.sig \
semver-${VERSION}.tar.gz
--key "https://raw.githubusercontent.com/sixafter/semver/main/cosign.pub" \
--bundle "semver-${VERSION}.tar.gz.sigstore.json" \
"semver-${VERSION}.tar.gz"

# ---------------------------------------------------------------------
# Verify checksums.txt using Sigstore bundle
# ---------------------------------------------------------------------

# Download checksums.txt and its signature from the latest release assets
curl -LO https://github.com/sixafter/semver/releases/download/${TAG}/checksums.txt
curl -LO https://github.com/sixafter/semver/releases/download/${TAG}/checksums.txt.sig
# Download checksums.txt and its bundle
curl -LO "https://github.com/sixafter/semver/releases/download/${TAG}/checksums.txt"
curl -LO "https://github.com/sixafter/semver/releases/download/${TAG}/checksums.txt.sigstore.json"

# Verify checksums.txt with cosign
# Verify the checksums.txt signature
cosign verify-blob \
--key https://raw.githubusercontent.com/sixafter/semver/main/cosign.pub \
--signature checksums.txt.sig \
--key "https://raw.githubusercontent.com/sixafter/semver/main/cosign.pub" \
--bundle "checksums.txt.sigstore.json" \
checksums.txt

# ---------------------------------------------------------------------
# Validate file integrity
# ---------------------------------------------------------------------

shasum -a 256 -c checksums.txt
```

If valid, Cosign will output:
Expand Down
95 changes: 95 additions & 0 deletions scripts/verify-mod.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
#!/bin/bash
# Copyright (c) 2024-2025 Six After, Inc.
#
# This source code is licensed under the Apache 2.0 License found in the
# LICENSE file in the root directory of this source tree.
set -euo pipefail

__dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
source "${__dir}"/os-type.sh

# Windows
if is_windows; then
echo "[ERROR] Windows is not currently supported." >&2
exit 1
fi

# Ensure tmp directory exists
mkdir -p tmp
rm tmp/*.zip 2>/dev/null || true

# ------------------------------------------------------------
# Detect latest release (README method)
# ------------------------------------------------------------
REPO_OWNER="sixafter"
REPO_NAME="semver"
MODULE="github.com/${REPO_OWNER}/${REPO_NAME}"

TAG=$(curl -s "https://api.github.com/repos/${REPO_OWNER}/${REPO_NAME}/releases/latest" | jq -r .tag_name)
VERSION=${TAG#v}

echo "Latest release: $TAG (version: $VERSION)"

# ------------------------------------------------------------
# Portable SHA-256 function (macOS + Linux)
# ------------------------------------------------------------
if command -v sha256sum >/dev/null 2>&1; then
SHA256="sha256sum"
else
SHA256="shasum -a 256"
fi

# ------------------------------------------------------------
# 1. GitHub Tag ZIP
# ------------------------------------------------------------
echo "Downloading GitHub tag archive..."
curl -sSfL -o tmp/github.zip \
"https://github.com/${REPO_OWNER}/${REPO_NAME}/archive/refs/tags/${TAG}.zip"

GITHUB_SHA=$($SHA256 tmp/github.zip | awk '{print $1}')
echo "GitHub ZIP SHA256: $GITHUB_SHA"

# ------------------------------------------------------------
# 2. Direct go mod ZIP
# ------------------------------------------------------------
echo "Downloading go mod ZIP using direct mode..."

MOD_JSON=$(GOPROXY=direct go mod download -json "${MODULE}@${TAG}")
MOD_ZIP_PATH=$(echo "$MOD_JSON" | jq -r '.Zip')

if [ ! -f "$MOD_ZIP_PATH" ]; then
echo "ERROR: The go mod ZIP path does not exist:"
echo "$MOD_ZIP_PATH"
exit 1
fi

cp "$MOD_ZIP_PATH" tmp/gomod.zip
GOMOD_SHA=$($SHA256 tmp/gomod.zip | awk '{print $1}')
echo "go mod ZIP SHA256: $GOMOD_SHA"

# ------------------------------------------------------------
# 3. Go Proxy ZIP
# ------------------------------------------------------------
echo "Downloading Go module proxy ZIP..."
curl -sSfL -o tmp/proxy.zip \
"https://proxy.golang.org/${MODULE}/@v/${TAG}.zip"

PROXY_SHA=$($SHA256 tmp/proxy.zip | awk '{print $1}')
echo "Proxy ZIP SHA256: $PROXY_SHA"

# ------------------------------------------------------------
# Comparison
# ------------------------------------------------------------
echo
echo "Comparing checksums..."
echo "GitHub : $GITHUB_SHA"
echo "go mod : $GOMOD_SHA"
echo "Proxy : $PROXY_SHA"
echo

if [ "$GITHUB_SHA" != "$GOMOD_SHA" ] || [ "$GITHUB_SHA" != "$PROXY_SHA" ]; then
echo "ERROR: CHECKSUM MISMATCH DETECTED!"
exit 1
fi

echo "Go module archive is fully reproducible across GitHub, direct, and proxy."
22 changes: 22 additions & 0 deletions scripts/verify-release.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
#!/bin/bash
# Copyright (c) 2024-2025 Six After, Inc.
#
# This source code is licensed under the Apache 2.0 License found in the
# LICENSE file in the root directory of this source tree.

# Verify the integrity of the latest release using Cosign + checksums
# Works on macOS and Linux

set -euo pipefail

__dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
source "${__dir}"/os-type.sh

# Windows
if is_windows; then
echo "[ERROR] Windows is not currently supported." >&2
exit 1
fi

rm -fr dist
goreleaser --config .goreleaser.yaml release --snapshot
152 changes: 152 additions & 0 deletions scripts/verify-sig.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
#!/usr/bin/env bash
# Copyright (c) 2024-2025 Six After, Inc.
#
# This source code is licensed under the Apache 2.0 License found in the
# LICENSE file in the root directory of this source tree.

set -euo pipefail

__dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
source "${__dir}/os-type.sh"

# Windows
if is_windows; then
echo "[ERROR] Windows is not currently supported." >&2
exit 1
fi

curl_retry() {
local url="$1"
local out="$2"
local attempt=1
local max=5
local delay=2

while true; do
# -f: fail on HTTP error codes
# -s: silent
# -S: show errors
# -L: follow redirects
if curl -fSsSL "${url}" -o "${out}"; then
return 0
fi

if (( attempt >= max )); then
echo "[ERROR] curl failed after ${attempt} attempts: ${url}" >&2
return 1
fi

echo "[WARN] curl failed (attempt ${attempt}/${max}). Retrying in ${delay}s..."
sleep $delay
attempt=$(( attempt + 1 ))
delay=$(( delay * 2 )) # exponential backoff
done
}

# ------------------------------------------------------------
# Project / repository name (portable)
# ------------------------------------------------------------
PROJECT="semver"
REPO="sixafter/${PROJECT}"
MODULE="github.com/${REPO}"

# tmp directory for artifacts
TMP="${__dir}/tmp"
mkdir -p "${TMP}"

echo "Project: ${PROJECT}"
echo "Repository: ${REPO}"
echo "Module path: ${MODULE}"
echo "Artifact directory: ${TMP}"
echo

# ------------------------------------------------------------
# Detect latest release
# ------------------------------------------------------------
TAG=$(curl -s "https://api.github.com/repos/${REPO}/releases/latest" | jq -r .tag_name)
VERSION=${TAG#v}

echo "Latest release: ${TAG} (version: ${VERSION})"

# ------------------------------------------------------------
# Determine SHA-256 tool
# ------------------------------------------------------------
if command -v sha256sum >/dev/null 2>&1; then
SHA256="sha256sum"
else
SHA256="shasum -a 256"
fi

# ------------------------------------------------------------
# Download release artifacts → tmp/
# ------------------------------------------------------------
echo
echo "Downloading release artifacts into ${TMP}..."

# Core tarball
curl_retry \
"https://github.com/${REPO}/releases/download/${TAG}/${PROJECT}-${VERSION}.tar.gz" \
"${TMP}/${PROJECT}-${VERSION}.tar.gz"

# Tarball signature
curl_retry \
"https://github.com/${REPO}/releases/download/${TAG}/${PROJECT}-${VERSION}.tar.gz.sigstore.json" \
"${TMP}/${PROJECT}-${VERSION}.tar.gz.sigstore.json"

# SBOM
curl_retry \
"https://github.com/${REPO}/releases/download/${TAG}/${PROJECT}-${VERSION}.tar.gz.sbom.json" \
"${TMP}/${PROJECT}-${VERSION}.tar.gz.sbom.json"

# checksums.txt
curl_retry \
"https://github.com/${REPO}/releases/download/${TAG}/checksums.txt" \
"${TMP}/checksums.txt"

# checksums.txt signature
curl_retry \
"https://github.com/${REPO}/releases/download/${TAG}/checksums.txt.sigstore.json" \
"${TMP}/checksums.txt.sigstore.json"

# ------------------------------------------------------------
# Verify tarball with Cosign
# ------------------------------------------------------------
echo
echo "Verifying tarball signature..."

cosign verify-blob \
--key "${__dir}/../cosign.pub" \
--bundle "${TMP}/${PROJECT}-${VERSION}.tar.gz.sigstore.json" \
"${TMP}/${PROJECT}-${VERSION}.tar.gz"

echo "Tarball signature OK."

# ------------------------------------------------------------
# Verify checksums manifest signature
# ------------------------------------------------------------
echo
echo "Verifying checksums.txt signature..."

cosign verify-blob \
--key "${__dir}/../cosign.pub" \
--bundle "${TMP}/checksums.txt.sigstore.json" \
"${TMP}/checksums.txt"

echo "Checksums signature OK."

# ------------------------------------------------------------
# Validate local artifact integrity
# ------------------------------------------------------------
echo
echo "Verifying file checksums locally..."
(
cd "${TMP}"
$SHA256 -c checksums.txt
) || {
echo
echo "❌ Release verification FAILED."
exit 1
}

echo
echo "✔ Release verification succeeded."
2 changes: 0 additions & 2 deletions vendor/modules.txt
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,6 @@ github.com/davecgh/go-spew/spew
# github.com/pmezard/go-difflib v1.0.0
## explicit
github.com/pmezard/go-difflib/difflib
# github.com/stretchr/objx v0.5.2
## explicit; go 1.20
# github.com/stretchr/testify v1.11.1
## explicit; go 1.17
github.com/stretchr/testify/assert
Expand Down