The OpenWRT page about download integrity verification describes three steps for a successful verification of downloaded files:
- Download the
sha256sum
andsha256sum.asc
files - Check the signature with
gpg --with-fingerprint --verify sha256sum.asc sha256sum
, ensure that the GnuPG command reports a good signature and that the fingerprint matches the ones listed on our fingerprints page. - 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
It has contained a convenience script in the past, but it was removed, due to being unsafe (check the wiki page history). The steps are left for the user to follow and seem straightforward but I believe a little explanation won't hurt. I'll use TP-Link MR200v1 as an example. The aim is to write commands in a way that can be automated using bash:
Download files
A simple wget
can serve:
url="https://downloads.openwrt.org/snapshots/targets/ramips/mt7620"
sumsFile="sha256sums"
wget "$url/$sumsFile"
wget "$url/$sumsFile.asc"
Files can be deleted before calling wget
, because it does filestamping
(adding an incremental number to the end of a filename).
Verify signature
Verifying signature in an automated manner using bash is where I spent most of my time. Simply running the offered command is not sufficient:
gpg --with-fingerprint --verify sha256sums.asc sha256sums
Without any previous work, it results into the following error:
gpg: Can't check signature: No public key
There are numerous ways around the problem, the most automatic is to use
--auto-key-retrieve
option:
gpg --auto-key-retrieve --with-fingerprint --verify sha256sums.asc
This option assumes, that the keys are published inside a key server. Also
note that gpg can assume the right filename if it matches with the
signature, so sha256sums
can be omitted. The output should look similar
to this:
gpg: assuming signed data in 'sha256sums'
gpg: Signature made Sun 04 Apr 2021 03:07:20 CEST
gpg: using RSA key 6D9278A33A9AB3146262DCECF93525A88B699029
gpg: Good signature from "LEDE Build System (LEDE GnuPG key for unattended build jobs) <[email protected]>" [unknown]
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
Visually the focusing on the word Good is the minimal requirement of the the first part second step:
ensure that the GnuPG command reports a good signature
Manually navigating to the
fingerprints page
and copy-search-paste in a browser for the actual fingerprint, for
instance 54CC 7430 7A2C 6DC9 CE61 8269 CD84 BCED 6264 71F1
in this
example is required as the second part of the second step.
hat the fingerprint matches the ones listed on our fingerprints page
Test return status
With a focus on automation, the fact that the gpg --verify
option returns
0 if the verification was successful and 1 of not (which is a common
convention), means testing for the status of the
previous command in bash with $?
can be used here:
gpg --auto-key-retrieve --with-fingerprint --verify sha256sums.asc
if [ $? -eq 0 ]; then
echo "SIGNATURE VERIFIED"
else
echo "SIGNATURE INVALID, the program will terminate"
exit 1
fi
Fingerprint parsing
Depending on the GnuPG version, the gpg --verify
only prints the
output to the screen. To parse the output and to do something useful with
it in the script, an
option --status-fd 1
has to be added as well:
gpg --status-fd 1 --auto-key-retrieve --with-fingerprint --verify sha256sums.asc
The output becomes formatted differently now:
[GNUPG:] NEWSIG
[GNUPG:] KEY_CONSIDERED 54CC74307A2C6DC9CE618269CD84BCED626471F1 0
[GNUPG:] SIG_ID 5XkMbTUScxodW5F+uzE34LmUBk8 2021-04-04 1617498440
[GNUPG:] KEY_CONSIDERED 54CC74307A2C6DC9CE618269CD84BCED626471F1 0
[GNUPG:] GOODSIG F93525A88B699029 LEDE Build System (LEDE GnuPG key for unattended build jobs) <[email protected]>
[GNUPG:] VALIDSIG 6D9278A33A9AB3146262DCECF93525A88B699029 2021-04-04 1617498440 0 4 0 1 10 00 54CC74307A2C6DC9CE618269CD84BCED626471F1
[GNUPG:] KEY_CONSIDERED 54CC74307A2C6DC9CE618269CD84BCED626471F1 0
[GNUPG:] KEY_CONSIDERED 54CC74307A2C6DC9CE618269CD84BCED626471F1 0
[GNUPG:] TRUST_UNDEFINED 0 pgp
[GNUPG:] VERIFICATION_COMPLIANCE_MODE 23
An example grep
command extracting the
fingerprint of the primary key:
gpgOutput=$(gpg --status-fd 1 --auto-key-retrieve --with-fingerprint --verify sha256sums.asc)
fingerprint=$(gpgOutput | grep -m1 KEY_CONSIDERED | tr -d' ' -f3)
echo $fingerprint
Checking that the fingerprint matches the one published is the most tricky part, because it is only printed on the web page with a custom formatting. Also, it is not foolproof, because should the attackers got hold of the webserver where the fingerprints are published, they could've substituted there different ones. This requires some cognitive effort on the user's part to do the research about what source can be trusted or not, but this is a topic for another day.
Fingerprint formatting
Note that some formatting of the fingerprint usually has to be done here to
match the published shape, for instance
adding a space every four characters
and adding one another space every 25 to match the user-readable output of
the gpg --verify
can be
done like this:
# formats fingerprint 54CC74307A2C6DC9CE618269CD84BCED626471F1
# to prettier form of 54CC 7430 7A2C 6DC9 CE61 8269 CD84 BCED 6264 71F1
formatted=$(echo "$fingerprint" | sed 's/.\{4\}/& /g' | xargs | sed 's/.\{25\}/& /g')
An example checking the presence of a fingerprint on a web page, assuming the fingerprints there are legit:
curl -s "$fingerprintUrl" | grep -o "$formatted"
The grep
command also follows the return status convention, so the same
test with $?
can be applied here, providing an automated solution.
Checksum verification
The third step. Now that the validity of the checksums is verified, they can be used for an actual image file verification, meaning that all the steps before here were just a preparation. But with everything in place, the steps are straightforward:
sumsFile="sha256sums"
url="https://downloads.openwrt.org/snapshots/targets/ramips/mt7620"
imageFile="openwrt-imagebuilder-ramips-mt7620.Linux-x86_64.tar.xz"
wget "$url/$imageFile"
sha256sum -c --ignore-missing "$sumsFile"
The output of the sha256sum
tells that the image file (or any other file
which sha256 sum is stored in the sha256sums file) was downloaded intact.
openwrt-imagebuilder-ramips-mt7620.Linux-x86_64.tar.xz: OK
The same status test can be employed here as well to automate the script further. Source files performing the steps are also available in the repository.
This is a 25th post of #100daystooffload.