Release Signing

LEDE uses both GnuPG and usign, a derivate of the OpenBSD signify utilitiy.

The OPKG package manager uses usign Ed25519 signatures to verify repository metadata when installing packages while release image files are usually signed by one or more developers with detached GPG signatures to allow users to verify the integrity of installation files.

Our usign signature files carry the extension .sig while the detached GPG signatures end with .gpg.

Note that not every file is signed individually but that we’re signing the sha256sums or - for repositories - the Packages files to establish a chain of trust: The SHA256 checksum will verify the integrity of the actual file while the signature will verify the integrity of the file containing the checksums.

Verify download integrity

In order to verify the integrity of a firmware download you need to do the following steps:

  1. Download the sha256sum and sha256sum.gpg files
  2. Check the signature with gpg --with-fingerprint --verify sha256sum.gpg sha256sum, ensure that the GnuPG command reports a good signature and that the fingerprint matches the ones listed on our fingerprints page.
  3. Download the firmware image into the same directory as the sha256sums file and verify its checksum using the following command: sha256sum -c --ignore-missing sha256sums

Verification script

You can use our supplied convenience script to automate the required download and signature verification steps.

Below is an example transcript of using the download.sh script.

user@host:~$ wget -O download.sh https://lede-project.org/_export/code/docs/user-guide/release_signatures?codeblock=1
--2016-12-24 01:48:14--  https://lede-project.org/_export/code/docs/user-guide/release_signatures?codeblock=1
Resolving lede-project.org (lede-project.org)... 139.59.209.225, 2a03:b0c0:3:d0::1af1:1
Connecting to lede-project.org (lede-project.org)|139.59.209.225|:443... connected.
HTTP request sent, awaiting response... 200 OK
Length: unspecified [text/plain]
Saving to: ‘download.sh’

    [ <=>                                                                                ] 4,091       --.-K/s   in 0s      

2016-12-24 01:48:14 (722 MB/s) - ‘download.sh’ saved [4091]

user@host:~$ chmod +x download.sh 
user@host:~$ ./download.sh https://downloads.lede-project.org/snapshots/targets/ar71xx/generic/lede-ar71xx-generic-tl-wr1043nd-v1-squashfs-factory.bin 
1) Downloading image file
=========================
############################################################ 100,0%

2) Downloading checksum file
============================
############################################################ 100,0%

3) Downloading the GPG signature
================================
############################################################ 100,0%

4) Verifying GPG signature
==========================
The signature was signed by a public key with the id F93525A88B699029 which is not present on this system.

Provide a public keyserver url below or press enter to accept the default suggestion. Hit Ctrl-C to abort the operation.

Keyserver to use? [hkp:%%//%%pool.sks-keyservers.net] > 
gpg: requesting key 8B699029 from hkp server pool.sks-keyservers.net 
gpg: key 626471F1: public key "LEDE Build System (LEDE GnuPG key for unattended build jobs) <<lede-adm@lists.infradead.org>>" imported 
gpg: Total number processed: 1 
gpg:               imported: 1 (RSA: 1) 
gpg: Signature made Di 02 Aug 2016 10:10:40 CEST using RSA key ID 8B699029 
gpg: Good signature from "LEDE Build System (LEDE GnuPG key for unattended build jobs) <<lede-adm@lists.infradead.org>>" 
gpg: WARNING: This key is not certified with a trusted signature! 
gpg:          There is no indication that the signature belongs to the owner. 
Primary key fingerprint: 54CC 7430 7A2C 6DC9 CE61 8269 CD84 BCED 6264 71F1 
     Subkey fingerprint: 6D92 78A3 3A9A B314 6262 DCEC F935 25A8 8B69 9029

5) Verifying SHA256 checksum
============================
lede-ar71xx-generic-tl-wr1043nd-v1-squashfs-factory.bin: OK

Verification done!
==================
Firmware image placed in ///home/user/lede-ar71xx-generic-tl-wr1043nd-v1-squashfs-factory.bin//.

Cleaning up. <user@host>:~$

Developer Information

Developers participating in the LEDE project need to provide both GnuPG and usign public keys which are stored in the central keyring.git repository.

Refer to the key generation howto page for instruction on how to generate suitable signing keys.

Download.sh

This convenience script automates key generation and verification.

Download.sh
#!/usr/bin/env bash
# Script to perform verified file downloads.
# Exit codes:
#  0 - File downloaded successfully and verified
#  1 - Failed to download requested file
#  2 - Failed to download sha256sums file
#  3 - Failed to download sha256sums.gpg file
#  4 - GnuPG is available but fails to verify the signature (missing pubkey, file integrity error, ...)
#  5 - The checksums do not match
#  6 - Unable to copy the requested file to its final destination
#  254 - The script got interrupted by a signal
#  255 - A suitable download or checksum utility is missing
 
[ -n "$1" ] || {
	echo "Usage: $0 <url>" >&2
	exit 1
}
 
finish() {
	[ -e "/tmp/verify.$$" ] && {
		echo "Cleaning up."
		rm -r "/tmp/verify.$$"
	}
	exit $1
}
 
trap "finish 254" INT TERM
 
destdir="$(pwd)"
image_url="$1"
image_file="${image_url##*/}"
sha256_url="${image_url%/*}/sha256sums"
gpgsig_url="${image_url%/*}/sha256sums.gpg"
keyserver_url="hkp://pool.sks-keyservers.net"
 
# Find a suitable download utility
if which curl >/dev/null; then
	download() { curl --progress-bar -o "$1" "$2"; }
elif which wget >/dev/null; then
	download() { wget -O "$1" "$2"; }
elif which fetch >/dev/null; then
	download() { fetch -o "$1" "$2"; }
else
	echo "No suitable download utility found, cannot download files!" >&2
	finish 255
fi
 
# Find a suitable checksum utility
if which sha256sum >/dev/null; then
	checksum() { sha256sum -c --ignore-missing "sha256sums"; }
elif which shasum >/dev/null; then
	checksum() {
		local sum="$(shasum -a 256 "$image_file")";
		grep -xF "${sum%% *} *$image_file" "sha256sums";
	}
else
	echo "No SHA256 checksum executable installed, cannot verify checksums!" >&2
	finish 255
fi
 
# Check for gpg availability
if which gpg >/dev/null; then
	runpgp() { gpg "$@"; }
else
	runpgp() {
		echo "WARNING: No GnuPG installed, cannot verify digital signature!" >&2
		return 0
	}
fi
 
mkdir -p "/tmp/verify.$$"
cd "/tmp/verify.$$"
 
echo ""
echo "1) Downloading image file"
echo "========================="
download "$image_file" "$image_url" || {
	echo "Failed to download image file!" >&2
	finish 1
}
 
echo ""
echo "2) Downloading checksum file"
echo "============================"
download "sha256sums" "$sha256_url" || {
	echo "Failed to download checksum file!" >&2
	finish 2
}
 
echo ""
echo "3) Downloading the GPG signature"
echo "================================"
download "sha256sums.gpg" "$gpgsig_url" || {
	echo "Failed to download GPG signature!" >&2
	finish 3
}
 
echo ""
echo "4) Verifying GPG signature"
echo "=========================="
missing_key=$(runpgp --status-fd 1 --with-fingerprint --verify \
	"sha256sums.gpg" "sha256sums" 2>/dev/null | sed -ne 's!^.* NO_PUBKEY !!p')
 
if [ -n "$missing_key" ]; then
	echo "The signature was signed by a public key with the id $missing_key" >&2
	echo "which is not present on this system."                              >&2
	echo ""                                                                  >&2
 
	echo "Provide a public keyserver url below or press enter to accept the" >&2
	echo "default suggestion. Hit Ctrl-C to abort the operation."            >&2
	echo ""                                                                  >&2
 
	while true; do
		printf "Keyserver to use? [$keyserver_url] > "
		read url; case "${url:-$keyserver_url}" in
			hkp://*)
				gpg --keyserver "${url:-$keyserver_url}" --recv-keys "$missing_key" || {
					echo "Failed to download public key." >&2
					finish 7
				}
				break
			;;
			*)
				echo "Expecting a key server url in the form 'hkp://hostname'." >&2
			;;
		esac
	done
fi
 
runpgp --with-fingerprint --verify "sha256sums.gpg" "sha256sums" || {
	echo "Failed to verify checksum file with GPG signature!" >&2
	finish 4
}
 
echo ""
echo "5) Verifying SHA256 checksum"
echo "============================"
checksum || {
	echo "Checksums do not match!" >&2
	finish 5
}
 
cp "$image_file" "$destdir/$image_file" || {
	echo "Failed to write '$destdir/$image_file'" >&2
	finish 6
}
 
echo ""
echo "Verification done!"
echo "=================="
echo "Firmware image placed in '$destdir/$image_file'."
echo ""
 
finish 0