]> Nutra Git (v1) - gamesguru/feather.git/commitdiff
guix: add release attestation
authortobtoht <tob@featherwallet.org>
Fri, 6 Oct 2023 13:38:54 +0000 (15:38 +0200)
committertobtoht <tob@featherwallet.org>
Fri, 6 Oct 2023 16:42:22 +0000 (18:42 +0200)
contrib/flatpak/make_flatpak.sh
contrib/guix/README.md
contrib/guix/guix-attest [new file with mode: 0755]
contrib/guix/guix-build
contrib/guix/guix-verify [new file with mode: 0644]
contrib/guix/libexec/build.sh

index e0d39a6645bad9963436271f4f5faacac88d770d..19b21d3c7f0c2af2df1cd216244e1c5e6189e4a6 100644 (file)
@@ -7,8 +7,10 @@ get_store_path() {
     find gnu/store -maxdepth 1 -type d -name "*$1*" | sort | head -n 1
 }
 
-mkdir -p /output/flatpak
-cd /output/flatpak
+mkdir /tmp-output
+
+mkdir -p /tmp-output/flatpak
+cd /tmp-output/flatpak
 
 # Create build dir
 mkdir build
@@ -81,4 +83,20 @@ ln -s "/${GUIX_PROFILE}/share/xml" share/xml
 # Setup profile symlink
 ln -s "/${GUIX_PROFILE}" profile
 
-chmod -R 555 .
\ No newline at end of file
+chmod -R 555 .
+
+cd /tmp-output
+
+find . -print0 \
+    | xargs -0r touch --no-dereference --date="@${SOURCE_DATE_EPOCH}"
+find . \
+    | sort \
+    | zip -X@ "${DISTNAME}-flatpak.zip" \
+    || ( rm -f "${DISTNAME}-flatpak.zip" && exit 1 )
+
+mv "${DISTNAME}-flatpak.zip" /output
+
+cd /output
+rm feather
+
+sha256sum "${DISTNAME}-flatpak.zip" > SHA256SUMS.part
\ No newline at end of file
index a0ec561875c36c61ed2059360c11a485305ad191..bb53df4858a41271126a2ed147aed3bd4f1eb59e 100644 (file)
@@ -54,6 +54,30 @@ worktree to save disk space:
 ./contrib/guix/guix-clean
 ```
 
+
+## Attesting to build outputs
+
+After you've cloned the `feather-sigs` repository, to attest to the current
+worktree's commit/tag:
+
+```
+env GUIX_SIGS_REPO=<path/to/feather-sigs> SIGNER=<gpg-key-name> ./contrib/guix/guix-attest
+```
+
+See `./contrib/guix/guix-attest --help` for more information on the various ways
+`guix-attest` can be invoked.
+
+## Verifying build output attestations
+
+After at least one other signer has uploaded their signatures to the `feather-sigs`
+repository:
+
+```
+git -C <path/to/feather-sigs> pull
+env GUIX_SIGS_REPO=<path/to/feather-sigs> ./contrib/guix/guix-verify
+```
+
+
 ## Common `guix-build` invocation patterns and examples
 
 ### Keeping caches outside of the worktree
diff --git a/contrib/guix/guix-attest b/contrib/guix/guix-attest
new file mode 100755 (executable)
index 0000000..5c1c669
--- /dev/null
@@ -0,0 +1,263 @@
+#!/usr/bin/env bash
+export LC_ALL=C
+set -e -o pipefail
+
+# Source the common prelude, which:
+#   1. Checks if we're at the top directory of the Feather Wallet repository
+#   2. Defines a few common functions and variables
+#
+# shellcheck source=libexec/prelude.bash
+source "$(dirname "${BASH_SOURCE[0]}")/libexec/prelude.bash"
+
+
+###################
+## Sanity Checks ##
+###################
+
+################
+# Required non-builtin commands should be invokable
+################
+
+check_tools cat env basename mkdir diff sort
+
+if [ -z "$NO_SIGN" ]; then
+    # make it possible to override the gpg binary
+    GPG=${GPG:-gpg}
+
+    # $GPG can contain extra arguments passed to the binary
+    # so let's check only the existence of arg[0]
+    # shellcheck disable=SC2206
+    GPG_ARRAY=($GPG)
+    check_tools "${GPG_ARRAY[0]}"
+fi
+
+################
+# Required env vars should be non-empty
+################
+
+cmd_usage() {
+cat <<EOF
+Synopsis:
+
+    env GUIX_SIGS_REPO=<path/to/feather-sigs> \\
+        SIGNER=GPG_KEY_NAME[=SIGNER_NAME] \\
+        [ NO_SIGN=1 ]
+      ./contrib/guix/guix-attest
+
+Example w/o overriding signing name:
+
+    env GUIX_SIGS_REPO=/home/user/feather-sigs \\
+        SIGNER=achow101 \\
+      ./contrib/guix/guix-attest
+
+Example overriding signing name:
+
+    env GUIX_SIGS_REPO=/home/user/feather-sigs \\
+        SIGNER=0x96AB007F1A7ED999=dongcarl \\
+      ./contrib/guix/guix-attest
+
+Example w/o signing, just creating SHA256SUMS:
+
+    env GUIX_SIGS_REPO=/home/user/feather-sigs \\
+        SIGNER=achow101 \\
+        NO_SIGN=1 \\
+      ./contrib/guix/guix-attest
+
+EOF
+}
+
+if [ -z "$GUIX_SIGS_REPO" ] || [ -z "$SIGNER" ]; then
+    cmd_usage
+    exit 1
+fi
+
+################
+# GUIX_SIGS_REPO should exist as a directory
+################
+
+if [ ! -d "$GUIX_SIGS_REPO" ]; then
+cat << EOF
+ERR: The specified GUIX_SIGS_REPO is not an existent directory:
+
+    '$GUIX_SIGS_REPO'
+
+Hint: Please clone the feather-sigs repository and point to it with the
+      GUIX_SIGS_REPO environment variable.
+
+EOF
+cmd_usage
+exit 1
+fi
+
+################
+# The key specified in SIGNER should be usable
+################
+
+IFS='=' read -r gpg_key_name signer_name <<< "$SIGNER"
+if [ -z "${signer_name}" ]; then
+    signer_name="$gpg_key_name"
+fi
+
+if [ -z "$NO_SIGN" ] && ! ${GPG} --dry-run --list-secret-keys "${gpg_key_name}" >/dev/null 2>&1; then
+    echo "ERR: GPG can't seem to find any key named '${gpg_key_name}'"
+    exit 1
+fi
+
+################
+# We should be able to find at least one output
+################
+
+echo "Looking for build output SHA256SUMS fragments in ${OUTDIR_BASE}"
+
+shopt -s nullglob
+sha256sum_fragments=( "$OUTDIR_BASE"/*/SHA256SUMS.part ) # This expands to an array of directories...
+shopt -u nullglob
+
+noncodesigned_fragments=()
+codesigned_fragments=()
+
+if (( ${#sha256sum_fragments[@]} )); then
+    echo "Found build output SHA256SUMS fragments:"
+    for outdir in "${sha256sum_fragments[@]}"; do
+        echo "    '$outdir'"
+        case "$outdir" in
+            "$OUTDIR_BASE"/*-codesigned/SHA256SUMS.part)
+                codesigned_fragments+=("$outdir")
+                ;;
+            *)
+                noncodesigned_fragments+=("$outdir")
+                ;;
+        esac
+    done
+    echo
+else
+    echo "ERR: Could not find any build output SHA256SUMS fragments in ${OUTDIR_BASE}"
+    exit 1
+fi
+
+##############
+##  Attest  ##
+##############
+
+# Usage: out_name $outdir
+#
+#   HOST: The output directory being attested
+#
+out_name() {
+    basename "$(dirname "$1")"
+}
+
+shasum_already_exists() {
+cat <<EOF
+--
+
+ERR: An ${1} file already exists for '${VERSION}' and attests
+     differently. You likely previously attested to a partial build (e.g. one
+     where you specified the HOST environment variable).
+
+     See the diff above for more context.
+
+Hint: You may wish to remove the existing attestations and their signatures by
+      invoking:
+
+          rm '${PWD}/${1}'{,.asc}
+
+      Then try running this script again.
+
+EOF
+}
+
+echo "Attesting to build outputs for version: '${VERSION}'"
+echo ""
+
+# Given a SHA256SUMS file as stdin that has lines like:
+#     0ba536819b221a91d3d42e978be016aac918f40984754d74058aa0c921cd3ea6  a/b/d/c/d/s/feather-2.5.0-riscv64-linux-gnu.tar.gz
+#     ...
+#
+# Replace each line's file name with its basename:
+#     0ba536819b221a91d3d42e978be016aac918f40984754d74058aa0c921cd3ea6  feather-2.5.0-riscv64-linux-gnu.tar.gz
+#     ...
+#
+basenameify_SHA256SUMS() {
+    sed -E 's@(^[[:xdigit:]]{64}[[:space:]]+).+/([^/]+$)@\1\2@'
+}
+
+outsigdir="$GUIX_SIGS_REPO/$VERSION/$signer_name"
+mkdir -p "$outsigdir"
+(
+    cd "$outsigdir"
+
+    temp_noncodesigned="$(mktemp)"
+    trap 'rm -rf -- "$temp_noncodesigned"' EXIT
+
+    if (( ${#noncodesigned_fragments[@]} )); then
+        cat "${noncodesigned_fragments[@]}" \
+            | sort -u \
+            | basenameify_SHA256SUMS \
+            | sort -k2 \
+                > "$temp_noncodesigned"
+        if [ -e noncodesigned.SHA256SUMS ]; then
+            # The SHA256SUMS already exists, make sure it's exactly what we
+            # expect, error out if not
+            if diff -u noncodesigned.SHA256SUMS "$temp_noncodesigned"; then
+                echo "A noncodesigned.SHA256SUMS file already exists for '${VERSION}' and is up-to-date."
+            else
+                shasum_already_exists noncodesigned.SHA256SUMS
+                exit 1
+            fi
+        else
+            mv "$temp_noncodesigned" noncodesigned.SHA256SUMS
+        fi
+    else
+        echo "ERR: No noncodesigned outputs found for '${VERSION}', exiting..."
+        exit 1
+    fi
+
+    temp_all="$(mktemp)"
+    trap 'rm -rf -- "$temp_all"' EXIT
+
+    if (( ${#codesigned_fragments[@]} )); then
+        # Note: all.SHA256SUMS attests to all of $sha256sum_fragments, but is
+        #       not needed if there are no $codesigned_fragments
+        cat "${sha256sum_fragments[@]}" \
+            | sort -u \
+            | sort -k2 \
+            | basenameify_SHA256SUMS \
+                > "$temp_all"
+        if [ -e all.SHA256SUMS ]; then
+            # The SHA256SUMS already exists, make sure it's exactly what we
+            # expect, error out if not
+            if diff -u all.SHA256SUMS "$temp_all"; then
+                echo "An all.SHA256SUMS file already exists for '${VERSION}' and is up-to-date."
+            else
+                shasum_already_exists all.SHA256SUMS
+                exit 1
+            fi
+        else
+            mv "$temp_all" all.SHA256SUMS
+        fi
+    else
+        # It is fine to have the codesigned outputs be missing (perhaps the
+        # detached codesigs have not been published yet), just print a log
+        # message instead of erroring out
+        echo "INFO: No codesigned outputs found for '${VERSION}', skipping..."
+    fi
+
+    if [ -z "$NO_SIGN" ]; then
+        echo "Signing SHA256SUMS to produce SHA256SUMS.asc"
+        for i in *.SHA256SUMS; do
+            if [ ! -e "$i".asc ]; then
+                ${GPG} --detach-sign \
+                       --digest-algo sha256 \
+                       --local-user "$gpg_key_name" \
+                       --armor \
+                       --output "$i".asc "$i"
+            else
+                echo "Signature already there"
+            fi
+        done
+    else
+        echo "Not signing SHA256SUMS as \$NO_SIGN is not empty"
+    fi
+    echo ""
+)
index c29701b6f8fd78c26063e3c808afd0232ec2727f..69c7488c5562b8f6c4bf66fbf94bf2076ec2b493 100755 (executable)
@@ -451,7 +451,7 @@ EOF
                 export VERSION=${VERSION}
                 time-machine pack -S /profile=. --manifest="${PWD}/contrib/guix/pack.scm"
                 PACK=$(time-machine pack --manifest="${PWD}/contrib/guix/pack.scm")
-                time-machine environment --ad-hoc unzip tar gzip findutils grep patchelf coreutils-minimal bash binutils sed \
+                time-machine environment --ad-hoc zip unzip tar gzip findutils grep patchelf coreutils-minimal bash binutils sed \
                                          --container \
                                          --pure \
                                          --no-cwd \
@@ -464,7 +464,8 @@ EOF
                                          --user="user" \
                                          ${SUBSTITUTE_URLS:+--substitute-urls="$SUBSTITUTE_URLS"} \
                                          ${ADDITIONAL_GUIX_COMMON_FLAGS} ${ADDITIONAL_GUIX_ENVIRONMENT_FLAGS} \
-                                         -- env SOURCE_DATE_EPOCH="${SOURCE_DATE_EPOCH:?unable to determine value}" \
+                                         -- env DISTNAME="$DISTNAME" \
+                                                SOURCE_DATE_EPOCH="${SOURCE_DATE_EPOCH:?unable to determine value}" \
                                                 VERSION="$VERSION" \
                                          bash -c "cd /feather && \
                                                   bash contrib/flatpak/make_flatpak.sh"
diff --git a/contrib/guix/guix-verify b/contrib/guix/guix-verify
new file mode 100644 (file)
index 0000000..02fc6f2
--- /dev/null
@@ -0,0 +1,174 @@
+#!/usr/bin/env bash
+export LC_ALL=C
+set -e -o pipefail
+
+# Source the common prelude, which:
+#   1. Checks if we're at the top directory of the Feather Wallet repository
+#   2. Defines a few common functions and variables
+#
+# shellcheck source=libexec/prelude.bash
+source "$(dirname "${BASH_SOURCE[0]}")/libexec/prelude.bash"
+
+
+###################
+## Sanity Checks ##
+###################
+
+################
+# Required non-builtin commands should be invokable
+################
+
+check_tools cat diff gpg
+
+################
+# Required env vars should be non-empty
+################
+
+cmd_usage() {
+cat <<EOF
+Synopsis:
+
+    env GUIX_SIGS_REPO=<path/to/feather-sigs> [ SIGNER=<signer> ] ./contrib/guix/guix-verify
+
+Example overriding signer's manifest to use as base
+
+    env GUIX_SIGS_REPO=/home/user/feather-sigs SIGNER=achow101 ./contrib/guix/guix-verify
+
+EOF
+}
+
+if [ -z "$GUIX_SIGS_REPO" ]; then
+    cmd_usage
+    exit 1
+fi
+
+################
+# GUIX_SIGS_REPO should exist as a directory
+################
+
+if [ ! -d "$GUIX_SIGS_REPO" ]; then
+cat << EOF
+ERR: The specified GUIX_SIGS_REPO is not an existent directory:
+
+    '$GUIX_SIGS_REPO'
+
+Hint: Please clone the feather-sigs repository and point to it with the
+      GUIX_SIGS_REPO environment variable.
+
+EOF
+cmd_usage
+exit 1
+fi
+
+##############
+##  Verify  ##
+##############
+
+OUTSIGDIR_BASE="${GUIX_SIGS_REPO}/${VERSION}"
+echo "Looking for signature directories in '${OUTSIGDIR_BASE}'"
+echo ""
+
+# Usage: verify compare_manifest current_manifest
+verify() {
+    local compare_manifest="$1"
+    local current_manifest="$2"
+    if ! gpg --quiet --batch --verify "$current_manifest".asc "$current_manifest" 1>&2; then
+        echo "ERR: Failed to verify GPG signature in '${current_manifest}'"
+        echo ""
+        echo "Hint: Either the signature is invalid or the public key is missing"
+        echo ""
+        failure=1
+    elif ! diff --report-identical "$compare_manifest" "$current_manifest" 1>&2; then
+        echo "ERR: The SHA256SUMS attestation in these two directories differ:"
+        echo "    '${compare_manifest}'"
+        echo "    '${current_manifest}'"
+        echo ""
+        failure=1
+    else
+        echo "Verified: '${current_manifest}'"
+        echo ""
+    fi
+}
+
+shopt -s nullglob
+all_noncodesigned=( "$OUTSIGDIR_BASE"/*/noncodesigned.SHA256SUMS )
+shopt -u nullglob
+
+echo "--------------------"
+echo ""
+if (( ${#all_noncodesigned[@]} )); then
+    compare_noncodesigned="${all_noncodesigned[0]}"
+    if [[ -n "$SIGNER" ]]; then
+        signer_noncodesigned="$OUTSIGDIR_BASE/$SIGNER/noncodesigned.SHA256SUMS"
+        if [[ -f "$signer_noncodesigned" ]]; then
+            echo "Using $SIGNER's manifest as the base to compare against"
+            compare_noncodesigned="$signer_noncodesigned"
+        else
+            echo "Unable to find $SIGNER's manifest, using the first one found"
+        fi
+    else
+        echo "No SIGNER provided, using the first manifest found"
+    fi
+
+    for current_manifest in "${all_noncodesigned[@]}"; do
+        verify "$compare_noncodesigned" "$current_manifest"
+    done
+
+    echo "DONE: Checking output signatures for noncodesigned.SHA256SUMS"
+    echo ""
+else
+    echo "WARN: No signature directories with noncodesigned.SHA256SUMS found"
+    echo ""
+fi
+
+shopt -s nullglob
+all_all=( "$OUTSIGDIR_BASE"/*/all.SHA256SUMS )
+shopt -u nullglob
+
+echo "--------------------"
+echo ""
+if (( ${#all_all[@]} )); then
+    compare_all="${all_all[0]}"
+    if [[ -n "$SIGNER" ]]; then
+        signer_all="$OUTSIGDIR_BASE/$SIGNER/all.SHA256SUMS"
+        if [[ -f "$signer_all" ]]; then
+            echo "Using $SIGNER's manifest as the base to compare against"
+            compare_all="$signer_all"
+        else
+            echo "Unable to find $SIGNER's manifest, using the first one found"
+        fi
+    else
+        echo "No SIGNER provided, using the first manifest found"
+    fi
+
+    for current_manifest in "${all_all[@]}"; do
+        verify "$compare_all" "$current_manifest"
+    done
+
+    # Sanity check: there should be no entries that exist in
+    # noncodesigned.SHA256SUMS that doesn't exist in all.SHA256SUMS
+    if [[ "$(comm -23 <(sort "$compare_noncodesigned") <(sort "$compare_all") | wc -c)" -ne 0 ]]; then
+        echo "ERR: There are unique lines in noncodesigned.SHA256SUMS which"
+        echo "     do not exist in all.SHA256SUMS, something went very wrong."
+        exit 1
+    fi
+
+    echo "DONE: Checking output signatures for all.SHA256SUMS"
+    echo ""
+else
+    echo "WARN: No signature directories with all.SHA256SUMS found"
+    echo ""
+fi
+
+echo "===================="
+echo ""
+if (( ${#all_noncodesigned[@]} + ${#all_all[@]} == 0 )); then
+    echo "ERR: Unable to perform any verifications as no signature directories"
+    echo "     were found"
+    echo ""
+    exit 1
+fi
+
+if [ -n "$failure" ]; then
+    exit 1
+fi
index 5875cf564fb0a0c3a32c9816183de8966ea5cbff..d535f7c7393c7dc1f354502e35448ebc0f8fe90b 100755 (executable)
@@ -458,7 +458,7 @@ mv --no-target-directory "$OUTDIR" "$ACTUAL_OUTDIR" \
     cd /outdir-base
     {
         echo "$GIT_ARCHIVE"
-        find "$ACTUAL_OUTDIR" -type f
+        find "$ACTUAL_OUTDIR" -type f -not -name "*.txt"
     } | xargs realpath --relative-base="$PWD" \
       | xargs sha256sum \
       | sort -k2 \