Skip to content

Commit

Permalink
.github, README: sign release archives with minisign (#557)
Browse files Browse the repository at this point in the history
Since commit f7c1225 ("build: upload checksums file", 2022-04-13),
each configlet release has a checksums file. The assets for the most
recent release were:

    configlet_4.0.0-beta.12_checksums_sha256.txt
    configlet_4.0.0-beta.12_linux_x86-64.tar.gz
    configlet_4.0.0-beta.12_macos_x86-64.tar.gz
    configlet_4.0.0-beta.12_windows_x86-64.zip

But confirming a checksum match only checks for accidental corruption.
Add signing with minisign [1] to the release process, such that the next
release will have a .minisig file for each archive:

    configlet_4.0.0-beta.13_checksums_sha256.txt
    configlet_4.0.0-beta.13_linux_x86-64.tar.gz
    configlet_4.0.0-beta.13_linux_x86-64.tar.gz.minisig
    configlet_4.0.0-beta.13_macos_x86-64.tar.gz
    configlet_4.0.0-beta.13_macos_x86-64.tar.gz.minisig
    configlet_4.0.0-beta.13_windows_x86-64.zip
    configlet_4.0.0-beta.13_windows_x86-64.zip.minisig

From the minisign docs [2]:

    Minisign is a dead simple tool to sign files and verify signatures.

    It is portable, lightweight, and uses the highly secure Ed25519 public-key signature system.

For example, to verify the above Linux x86-64 release archive, run:

    minisign -Vm configlet_4.0.0-beta.13_linux_x86-64.tar.gz -P RWQGj6DTXgYLhKvWJMGtbDUrZerawUcyWnti9MGuWMx7VDW9DqZn2tMZ

where the argument to -P is the configlet minisign public key.
Alternatively, you can download the configlet-minisign.pub file and run:

    minisign -Vm configlet_4.0.0-beta.13_linux_x86-64.tar.gz -p configlet-minisign.pub

Minisign is an implementation of OpenBSD's signify protocol. For more
background, see posts on signify [3] and problems with PGP [4].

An alternative would be to sign releases with an SSH key [5], but
minisign is more focused, always uses Ed25519, and is simpler.
Especially for verification.

Some other projects that sign releases with minisign or signify:

- libsodium [6]

- OpenBSD [7]

- Void Linux [8]

- Zig [9]

- LibreSSL [10]

For now, the fetch-configlet scripts don't support verifying signatures.
They may be able to do so eventually, but it won't be required: we don't
want to require the user to have minisign installed.

Don't upload a signature for the checksum file, because:

- We want to optimize for the simplicity of verifying a single configlet
  release archive, so it's best to have only one way to do it: verify
  the signature for that archive.

- It's harder to explain the alternative: verify the signature for the
  checksum file AND check that the sha256 of the release archive matches
  that in the checksum file.

- It doesn't matter that verifying `n` configlet release archives
  requires running `n` minisign commands, rather than exactly 1 minisign
  command plus 1 `sha256sum --check` (or `sha256 -c` on some platforms)
  command.

[1] https://github.com/jedisct1/minisign
[2] https://jedisct1.github.io/minisign/
[3] https://www.openbsd.org/papers/bsdcan-signify.html
[4] https://latacora.micro.blog/2019/07/16/the-pgp-problem.html
[5] https://www.agwa.name/blog/post/ssh_signatures
[6] https://github.com/jedisct1/libsodium/releases/tag/1.0.18-RELEASE
[7] https://www.openbsd.org/faq/faq4.html#Download
[8] https://docs.voidlinux.org/installation/index.html#verifying-digital-signature
[9] https://github.com/ziglang/zig/releases/tag/0.11.0
[10] https://www.libressl.org/releases.html

Closes: #548
  • Loading branch information
ee7 authored Aug 9, 2023
1 parent fa7d0bb commit 009dc9d
Show file tree
Hide file tree
Showing 7 changed files with 130 additions and 20 deletions.
29 changes: 29 additions & 0 deletions .github/bin/linux-install-minisign
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
#!/usr/bin/env sh

set -e

tag='0.11'
name="minisign-${tag}-linux"
archive_name="${name}.tar.gz"
url="https://github.com/jedisct1/minisign/releases/download/${tag}/${archive_name}"

curl -sSfL --retry 3 "${url}" > "${archive_name}"

# The minisign archive signature was verified previously:
#
# $ minisign -Vm minisign-0.11-linux.tar.gz -P RWQf6LRCGA9i53mlYecO4IzT51TGPpvWucNSCh1CBM0QTaLn73Y7GFO3
# Signature and comment signature verified
# Trusted comment: timestamp:1673952371 file:minisign-0.11-linux.tar.gz hashed
#
# Therefore we can trust the downloaded minisign if it has the corresponding hash.

sha256sum_input="f0a0954413df8531befed169e447a66da6868d79052ed7e892e50a4291af7ae0 ${archive_name}"

if echo "${sha256sum_input}" | sha256sum --check; then
tar xf "${archive_name}"
mv "minisign-linux/x86_64/minisign" /usr/local/bin/
rm -rf "minisign-linux" "${archive_name}"
else
echo "sha256sum of downloaded minisign archive is unexpected. Exiting"
exit 1
fi
17 changes: 0 additions & 17 deletions .github/bin/upload-checksums-file

This file was deleted.

57 changes: 57 additions & 0 deletions .github/bin/upload-signatures-and-checksums
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
#!/usr/bin/env sh

set -e

build_tag="${GITHUB_REF_NAME}"

# Download every release asset
download_dir="releases/${build_tag}"
gh release download "${build_tag}" --dir "${download_dir}"

# Write secret key to expected location
if [ -n "${CONFIGLET_MINISIGN_SECRET_KEY}" ]; then
minisign_dir="${HOME}/.minisign"
mkdir -p "${minisign_dir}"
printenv CONFIGLET_MINISIGN_SECRET_KEY > "${minisign_dir}/minisign.key"
else
printf "CONFIGLET_MINISIGN_SECRET_KEY environment variable not found. Exiting."
exit 1
fi

# Get the configlet public key from a file in the repo
configlet_minisign_public_key="$(tail -n1 configlet-minisign.pub)"

cd "${download_dir}" || exit

# Write checksums file
# Don't include checksums of the minisig files
checksums_file="configlet_${build_tag}_checksums_sha256.txt"
sha256sum -- * > "${checksums_file}"

for file in *; do
# Create minisig file with a custom trusted comment
dt="$(date -u '+%Y-%m-%dT%H:%M:%SZ')" # Like `date --utc --iso=seconds`
# The below is the same format as minisign's default, but using a non-unix timestamp.
trusted_comment="timestamp:${dt} file:${file} hashed"
minisign -Sm "${file}" -t "${trusted_comment}"

# Verify the signed file
minisign -Vm "${file}" -P "${configlet_minisign_public_key}"
done

# Don't upload the signature for the checksum file, because:
#
# - we want to optimize for the simplicity of verifying a single configlet
# release archive, so it's best to have only one way to do it (verify the signature for
# that archive).
#
# - it's harder to explain the alternative: verify the signature for the checksum file
# AND check that the sha256 of the release archive matches that in the checksum file.
#
# - it doesn't matter that verifying `n` configlet release archives requires running `n`
# minisign commands, rather than exactly 1 minisign command plus 1 `sha256sum --check`
# command.
rm "${checksums_file}.minisig"

# Upload checksums file and signature files
gh release upload "${build_tag}" "${checksums_file}" ./*.minisig
14 changes: 11 additions & 3 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ jobs:

name: "${{ matrix.os }}-${{ matrix.arch }}"
runs-on: ${{ matrix.runs-on }}
permissions:
contents: write
steps:
- name: Checkout code
uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9
Expand Down Expand Up @@ -63,12 +65,18 @@ jobs:
checksums:
needs: [build]
runs-on: ubuntu-22.04
name: Upload checksums file
name: Upload signatures and checksums
permissions:
contents: write
steps:
- name: Checkout code
uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9

- name: Upload checksums file
run: ./.github/bin/upload-checksums-file
- name: Install minisign
run: ./.github/bin/linux-install-minisign

- name: Upload signatures and checksums
run: ./.github/bin/upload-signatures-and-checksums
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
CONFIGLET_MINISIGN_SECRET_KEY: ${{ secrets.CONFIGLET_MINISIGN_SECRET_KEY }}
2 changes: 2 additions & 0 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ jobs:

name: "${{ matrix.os }}-${{ matrix.arch }}"
runs-on: ${{ matrix.runs-on }}
permissions:
contents: read
steps:
- name: Checkout code
uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9
Expand Down
29 changes: 29 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,35 @@ The first is a bash script, and the second is a PowerShell script.
Running one of these scripts downloads the latest version of configlet to the `bin` directory.
You can then use configlet by running `bin/configlet` or `bin/configlet.exe` respectively.

### Verifying

We sign each configlet release archive with [`minisign`](https://jedisct1.github.io/minisign/).

For now, if you want to verify the signature of a configlet release, you need to do it manually.
The `fetch-configlet` script may support checking the release signature in the future, but it won't be required: we don't want to require every `fetch-configlet` user to install `minisign`.

To verify a release archive, first download (from the assets section of a [release](https://github.com/exercism/configlet/releases)) the archive and its corresponding `.minisig` file.
Write them to the same directory.
For example, to verify the configlet 4.0.0-beta.13 Linux x86-64 release, download these files to the same directory:

```text
configlet_4.0.0-beta.13_linux_x86-64.tar.gz
configlet_4.0.0-beta.13_linux_x86-64.tar.gz.minisig
```

Then run a `minisign` command in that directory:

```shell
minisign -Vm configlet_4.0.0-beta.13_linux_x86-64.tar.gz -P RWQGj6DTXgYLhKvWJMGtbDUrZerawUcyWnti9MGuWMx7VDW9DqZn2tMZ
```

where the above argument to `-P` is the configlet public key.

The above command has verified the release archive if (and only if) the command's output contains `Signature and comment signature verified`.

Then extract the archive to obtain the (now-verified) configlet executable.
You may delete the archive and the `.minisig` file.

## Usage

The application is a single binary and can be used as follows:
Expand Down
2 changes: 2 additions & 0 deletions configlet-minisign.pub
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
untrusted comment: configlet minisign public key, created 2023-07-18
RWQGj6DTXgYLhKvWJMGtbDUrZerawUcyWnti9MGuWMx7VDW9DqZn2tMZ

1 comment on commit 009dc9d

@ee7
Copy link
Member Author

@ee7 ee7 commented on 009dc9d Aug 9, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To clarify this part of the commit message:

For example, to verify the above Linux x86-64 release archive, run:

minisign -Vm configlet_4.0.0-beta.13_linux_x86-64.tar.gz -P RWQGj6DTXgYLhKvWJMGtbDUrZerawUcyWnti9MGuWMx7VDW9DqZn2tMZ

where the argument to -P is the configlet minisign public key.
Alternatively, you can download the configlet-minisign.pub file and run:

minisign -Vm configlet_4.0.0-beta.13_linux_x86-64.tar.gz -p configlet-minisign.pub

You need to have the corresponding .minisig file in the same directory when you run one of the commands. The README instructions are clearer.

Please sign in to comment.