#!/bin/bash REPO=lightningnetwork PROJECT=lnd RELEASE_URL=https://github.com/$REPO/$PROJECT/releases API_URL=https://api.github.com/repos/$REPO/$PROJECT/releases SIGNATURE_SELECTOR=". | select(.name | test(\"manifest-.*(\\\\.txt\\\\.asc)$\")) | .name" HEADER_JSON="Accept: application/json" HEADER_GH_JSON="Accept: application/vnd.github.v3+json" # All keys that can sign lnd releases. The key must be downloadable/importable # from the URL given after the space. KEYS=() KEYS+=("F4FC70F07310028424EFC20A8E4256593F177720 https://keybase.io/guggero/pgp_keys.asc") KEYS+=("15E7ECF257098A4EF91655EB4CA7FE54A6213C91 https://keybase.io/carlakirkcohen/pgp_keys.asc") KEYS+=("9C8D61868A7C492003B2744EE7D737B67FA592C7 https://keybase.io/bitconner/pgp_keys.asc") KEYS+=("E4D85299674B2D31FAA1892E372CBD7633C61696 https://keybase.io/roasbeef/pgp_keys.asc") KEYS+=("729E9D9D92C75A5FBFEEE057B5DD717BEF7CA5B1 https://keybase.io/wpaulino/pgp_keys.asc") KEYS+=("7E81EF6B9989A9CC93884803118759E83439A9B1 https://keybase.io/eugene_/pgp_keys.asc") KEYS+=("7AB3D7F5911708842796513415BAADA29DA20D26 https://keybase.io/halseth/pgp_keys.asc") function check_command() { echo -n "Checking if $1 is installed... " if ! command -v "$1"; then echo "ERROR: $1 is not installed or not in PATH!" exit 1 fi } # By default we're picking up lnd and lncli from the system $PATH. LND_BIN=$(which lnd) LNCLI_BIN=$(which lncli) # If exactly two parameters are specified, we expect the first one to be lnd and # the second one to be lncli. if [[ $# -eq 2 ]]; then LND_BIN=$(realpath $1) LNCLI_BIN=$(realpath $2) # Make sure both files actually exist. if [[ ! -f $LND_BIN ]]; then echo "ERROR: $LND_BIN not found!" exit 1 fi if [[ ! -f $LNCLI_BIN ]]; then echo "ERROR: $LNCLI_BIN not found!" exit 1 fi elif [[ $# -eq 0 ]]; then # Make sure both binaries can be found and are executable. check_command lnd check_command lncli else echo "ERROR: invalid number of parameters!" echo "Usage: verify-install.sh [lnd-binary lncli-binary]" exit 1 fi check_command curl check_command jq check_command gpg LND_VERSION=$($LND_BIN --version | cut -d'=' -f2) LNCLI_VERSION=$($LNCLI_BIN --version | cut -d'=' -f2) LND_SUM=$(sha256sum $LND_BIN | cut -d' ' -f1) LNCLI_SUM=$(sha256sum $LNCLI_BIN | cut -d' ' -f1) echo "Detected lnd $LND_BIN version $LND_VERSION with SHA256 sum $LND_SUM" echo "Detected lncli $LNCLI_BIN version $LNCLI_VERSION with SHA256 sum $LNCLI_SUM" # Make sure lnd and lncli are installed with the same version and is an actual # version string. if [[ "$LNCLI_VERSION" != "$LND_VERSION" ]]; then echo "ERROR: Version $LNCLI_VERSION of lncli does not match $LND_VERSION of lnd!" exit 1 fi version_regex="^v[[:digit:]]+\.[[:digit:]]+\.[[:digit:]]" if [[ ! "$LND_VERSION" =~ $version_regex ]]; then echo "ERROR: Invalid version of lnd detected: $LND_VERSION" exit 1 fi # If we're inside the docker image, there should be a shasums.txt file in the # root directory. If that's the case, we first want to make sure we still have # the same hash as we did when building the image. if [[ -f /shasums.txt ]]; then if ! grep -q "$LND_SUM" /shasums.txt; then echo "ERROR: Hash $LND_SUM for lnd not found in /shasums.txt: " cat /shasums.txt exit 1 fi if ! grep -q "$LNCLI_SUM" /shasums.txt; then echo "ERROR: Hash $LNCLI_SUM for lnd not found in /shasums.txt: " cat /shasums.txt exit 1 fi fi # Import all the signing keys. for key in "${KEYS[@]}"; do KEY_ID=$(echo $key | cut -d' ' -f1) IMPORT_URL=$(echo $key | cut -d' ' -f2) echo "Downloading and importing key $KEY_ID from $IMPORT_URL" curl -L -s $IMPORT_URL | gpg --import # Make sure we actually imported the correct key. if ! gpg --list-key "$KEY_ID"; then echo "ERROR: Imported key from $IMPORT_URL doesn't match ID $KEY_ID." fi done echo "" # Download the JSON of the release itself. That'll contain the release ID we need for the next call. RELEASE_JSON=$(curl -L -s -H "$HEADER_JSON" "$RELEASE_URL/$LND_VERSION") TAG_NAME=$(echo $RELEASE_JSON | jq -r '.tag_name') RELEASE_ID=$(echo $RELEASE_JSON | jq -r '.id') echo "Release $TAG_NAME found with ID $RELEASE_ID" # Now download the asset list and filter by manifests and signatures. ASSETS=$(curl -L -s -H "$HEADER_GH_JSON" "$API_URL/$RELEASE_ID" | jq -c '.assets[]') SIGNATURES=$(echo $ASSETS | jq -r "$SIGNATURE_SELECTOR") # Download all "manifest-*.txt.asc" as those contain both the hashes that were # signed and the signature itself (=detached sig). TEMP_DIR=$(mktemp -d /tmp/lnd-sig-verification-XXXXXX) for signature in $SIGNATURES; do echo "Downloading $signature" curl -L -s -o "$TEMP_DIR/$signature" "$RELEASE_URL/download/$LND_VERSION/$signature" done echo "" cd $TEMP_DIR || exit 1 NUM_CHECKS=0 for signature in $SIGNATURES; do # First make sure the downloaded signature file is valid. echo "Verifying $signature" if gpg --verify "$signature" 2>&1 | grep -q "Good signature"; then echo "Signature for $signature checks out: " gpg --verify "$signature" 2>&1 | grep "using" elif gpg --verify "$signature" 2>&1 | grep -q "No public key"; then echo "Unable to verify signature $signature, no key available, skipping" continue else echo "ERROR: Did not get valid signature for $signature!" exit 1 fi echo "" # Then make sure that the hash of the installed binaries can be found in the # signed list of hashes. if ! grep -q "$LND_SUM" "$signature"; then echo "ERROR: Hash $LND_SUM for lnd not found in $signature: " cat "$signature" exit 1 fi if ! grep -q "$LNCLI_SUM" "$signature"; then echo "ERROR: Hash $LNCLI_SUM for lncli not found in $signature: " cat "$signature" exit 1 fi echo "Verified lnd and lncli hashes against $signature" ((NUM_CHECKS=NUM_CHECKS+1)) done # We want at least one signature that signs the hashes of the binaries we have # installed. If we arrive here without exiting, it means no signature manifests # were uploaded (yet) with the correct naming pattern. if [[ $NUM_CHECKS -lt 1 ]]; then echo "ERROR: No valid signatures found!" echo "Make sure the release $LND_VERSION contains any signed manifests." exit 1 fi echo "" echo "SUCCESS! Verified lnd and lncli against $NUM_CHECKS signature(s)."