guix-attest raw

   1  #!/usr/bin/env bash
   2  export LC_ALL=C
   3  set -e -o pipefail
   4  
   5  # Source the common prelude, which:
   6  #   1. Checks if we're at the top directory of the Bitcoin Core repository
   7  #   2. Defines a few common functions and variables
   8  #
   9  # shellcheck source=libexec/prelude.bash
  10  source "$(dirname "${BASH_SOURCE[0]}")/libexec/prelude.bash"
  11  
  12  
  13  ###################
  14  ## Sanity Checks ##
  15  ###################
  16  
  17  ################
  18  # Required non-builtin commands should be invokable
  19  ################
  20  
  21  check_tools cat env basename mkdir diff sort
  22  
  23  if [ -z "$NO_SIGN" ]; then
  24      # make it possible to override the gpg binary
  25      GPG=${GPG:-gpg}
  26  
  27      # $GPG can contain extra arguments passed to the binary
  28      # so let's check only the existence of arg[0]
  29      # shellcheck disable=SC2206
  30      GPG_ARRAY=($GPG)
  31      check_tools "${GPG_ARRAY[0]}"
  32  fi
  33  
  34  ################
  35  # Required env vars should be non-empty
  36  ################
  37  
  38  cmd_usage() {
  39  cat <<EOF
  40  Synopsis:
  41  
  42      env GUIX_SIGS_REPO=<path/to/guix.sigs> \\
  43          SIGNER=GPG_KEY_NAME[=SIGNER_NAME] \\
  44          [ NO_SIGN=1 ]
  45        ./contrib/guix/guix-attest
  46  
  47  Example w/o overriding signing name:
  48  
  49      env GUIX_SIGS_REPO=/home/achow101/guix.sigs \\
  50          SIGNER=achow101 \\
  51        ./contrib/guix/guix-attest
  52  
  53  Example overriding signing name:
  54  
  55      env GUIX_SIGS_REPO=/home/dongcarl/guix.sigs \\
  56          SIGNER=0x96AB007F1A7ED999=dongcarl \\
  57        ./contrib/guix/guix-attest
  58  
  59  Example w/o signing, just creating SHA256SUMS:
  60  
  61      env GUIX_SIGS_REPO=/home/achow101/guix.sigs \\
  62          SIGNER=achow101 \\
  63          NO_SIGN=1 \\
  64        ./contrib/guix/guix-attest
  65  
  66  EOF
  67  }
  68  
  69  if [ -z "$GUIX_SIGS_REPO" ] || [ -z "$SIGNER" ]; then
  70      cmd_usage
  71      exit 1
  72  fi
  73  
  74  ################
  75  # GUIX_SIGS_REPO should exist as a directory
  76  ################
  77  
  78  if [ ! -d "$GUIX_SIGS_REPO" ]; then
  79  cat << EOF
  80  ERR: The specified GUIX_SIGS_REPO is not an existent directory:
  81  
  82      '$GUIX_SIGS_REPO'
  83  
  84  Hint: Please clone the guix.sigs repository and point to it with the
  85        GUIX_SIGS_REPO environment variable.
  86  
  87  EOF
  88  cmd_usage
  89  exit 1
  90  fi
  91  
  92  ################
  93  # The key specified in SIGNER should be usable
  94  ################
  95  
  96  IFS='=' read -r gpg_key_name signer_name <<< "$SIGNER"
  97  if [ -z "${signer_name}" ]; then
  98      signer_name="$gpg_key_name"
  99  fi
 100  
 101  if [ -z "$NO_SIGN" ] && ! ${GPG} --dry-run --list-secret-keys "${gpg_key_name}" >/dev/null 2>&1; then
 102      echo "ERR: GPG can't seem to find any key named '${gpg_key_name}'"
 103      exit 1
 104  fi
 105  
 106  ################
 107  # We should be able to find at least one output
 108  ################
 109  
 110  echo "Looking for build output SHA256SUMS fragments in ${OUTDIR_BASE}"
 111  
 112  shopt -s nullglob
 113  sha256sum_fragments=( "$OUTDIR_BASE"/*/SHA256SUMS.part ) # This expands to an array of directories...
 114  shopt -u nullglob
 115  
 116  noncodesigned_fragments=()
 117  codesigned_fragments=()
 118  
 119  if (( ${#sha256sum_fragments[@]} )); then
 120      echo "Found build output SHA256SUMS fragments:"
 121      for outdir in "${sha256sum_fragments[@]}"; do
 122          echo "    '$outdir'"
 123          case "$outdir" in
 124              "$OUTDIR_BASE"/*-codesigned/SHA256SUMS.part)
 125                  codesigned_fragments+=("$outdir")
 126                  ;;
 127              *)
 128                  noncodesigned_fragments+=("$outdir")
 129                  ;;
 130          esac
 131      done
 132      echo
 133  else
 134      echo "ERR: Could not find any build output SHA256SUMS fragments in ${OUTDIR_BASE}"
 135      exit 1
 136  fi
 137  
 138  ##############
 139  ##  Attest  ##
 140  ##############
 141  
 142  shasum_already_exists() {
 143  cat <<EOF
 144  --
 145  
 146  ERR: An ${1} file already exists for '${VERSION}' and attests
 147       differently. You likely previously attested to a partial build (e.g. one
 148       where you specified the HOST environment variable).
 149  
 150       See the diff above for more context.
 151  
 152  Hint: You may wish to remove the existing attestations and their signatures by
 153        invoking:
 154  
 155            rm '${PWD}/${1}'{,.asc}
 156  
 157        Then try running this script again.
 158  
 159  EOF
 160  }
 161  
 162  echo "Attesting to build outputs for version: '${VERSION}'"
 163  echo ""
 164  
 165  # Given a SHA256SUMS file as stdin that has lines like:
 166  #     0ba536819b221a91d3d42e978be016aac918f40984754d74058aa0c921cd3ea6  a/b/d/c/d/s/bitcoin-22.0rc2-riscv64-linux-gnu.tar.gz
 167  #     ...
 168  #
 169  # Replace each line's file name with its basename:
 170  #     0ba536819b221a91d3d42e978be016aac918f40984754d74058aa0c921cd3ea6  bitcoin-22.0rc2-riscv64-linux-gnu.tar.gz
 171  #     ...
 172  #
 173  basenameify_SHA256SUMS() {
 174      sed -E 's@(^[[:xdigit:]]{64}[[:space:]]+).+/([^/]+$)@\1\2@'
 175  }
 176  
 177  outsigdir="$GUIX_SIGS_REPO/$VERSION/$signer_name"
 178  mkdir -p "$outsigdir"
 179  (
 180      cd "$outsigdir"
 181  
 182      temp_noncodesigned="$(mktemp)"
 183      trap 'rm -rf -- "$temp_noncodesigned"' EXIT
 184  
 185      if (( ${#noncodesigned_fragments[@]} )); then
 186          cat "${noncodesigned_fragments[@]}" \
 187              | sort -u \
 188              | sort -k2 \
 189              | basenameify_SHA256SUMS \
 190                  > "$temp_noncodesigned"
 191          if [ -e noncodesigned.SHA256SUMS ]; then
 192              # The SHA256SUMS already exists, make sure it's exactly what we
 193              # expect, error out if not
 194              if diff -u noncodesigned.SHA256SUMS "$temp_noncodesigned"; then
 195                  echo "A noncodesigned.SHA256SUMS file already exists for '${VERSION}' and is up-to-date."
 196              else
 197                  shasum_already_exists noncodesigned.SHA256SUMS
 198                  exit 1
 199              fi
 200          else
 201              mv "$temp_noncodesigned" noncodesigned.SHA256SUMS
 202          fi
 203      else
 204          echo "ERR: No noncodesigned outputs found for '${VERSION}', exiting..."
 205          exit 1
 206      fi
 207  
 208      temp_all="$(mktemp)"
 209      trap 'rm -rf -- "$temp_all"' EXIT
 210  
 211      if (( ${#codesigned_fragments[@]} )); then
 212          # Note: all.SHA256SUMS attests to all of $sha256sum_fragments, but is
 213          #       not needed if there are no $codesigned_fragments
 214          cat "${sha256sum_fragments[@]}" \
 215              | sort -u \
 216              | sort -k2 \
 217              | basenameify_SHA256SUMS \
 218                  > "$temp_all"
 219          if [ -e all.SHA256SUMS ]; then
 220              # The SHA256SUMS already exists, make sure it's exactly what we
 221              # expect, error out if not
 222              if diff -u all.SHA256SUMS "$temp_all"; then
 223                  echo "An all.SHA256SUMS file already exists for '${VERSION}' and is up-to-date."
 224              else
 225                  shasum_already_exists all.SHA256SUMS
 226                  exit 1
 227              fi
 228          else
 229              mv "$temp_all" all.SHA256SUMS
 230          fi
 231      else
 232          # It is fine to have the codesigned outputs be missing (perhaps the
 233          # detached codesigs have not been published yet), just print a log
 234          # message instead of erroring out
 235          echo "INFO: No codesigned outputs found for '${VERSION}', skipping..."
 236      fi
 237  
 238      if [ -z "$NO_SIGN" ]; then
 239          echo "Signing SHA256SUMS to produce SHA256SUMS.asc"
 240          for i in *.SHA256SUMS; do
 241              if [ ! -e "$i".asc ]; then
 242                  ${GPG} --detach-sign \
 243                         --digest-algo sha256 \
 244                         --local-user "$gpg_key_name" \
 245                         --armor \
 246                         --output "$i".asc "$i"
 247              else
 248                  echo "Signature already there"
 249              fi
 250          done
 251      else
 252          echo "Not signing SHA256SUMS as \$NO_SIGN is not empty"
 253      fi
 254      echo ""
 255  )
 256