Retrieving and installation of Linux software can be done via package management systems (like apt) or manually downloading and running the executable.
Public Key Cryptography is used on Linux to verify the identity of the file provider. A public and private key are created, file sender signs the file with private key and publishes public key. Receiver uses the public key to verify the signature.
GNU Privacy Guard (GnuPG or GPG) is set of open-source tools for key exchange, signing, verification, encrypting and decrypting files.
I want to show here how to use gpg to verify a file downloaded from Internet on the example of Tor browser installer. It is not necessary to do this when using package managers to install some software as they use this automatically (see SecureApt - Debian Wiki).
From Tor's website we need to download:
1) an installation archive (https://www.torproject.org/dist/torbrowser/10.0.10/tor-browser-linux64-10.0.10_en-US.tar.xz)
2) its PGP signature (https://dist.torproject.org/torbrowser/10.0.10/tor-browser-linux64-10.0.10_en-US.tar.xz.asc) which looks like this:
-----BEGIN PGP SIGNATURE-----
iQIcBAABCgAGBQJgG5QbAAoJEOt3RJHZ/wbinYQP/RVTZOY9YLe7y2azJs2K3IcF
Wr20e2zMGVGpvV2WZ3uYij7nMQGujPU4V8cJ/5vSb3ONKSwr/YXfqSGOC6gL8EAF
yvJsS5JjVdGYpBxg9ye1Cb3AFtKgQLBwFLvDWtWuLH1lk7VDLY5dDPbRpX6x3HKE
LbulR1IsUdQLdrbUfkoUpYBf1GTb+4+F4a//tLEnCwWojO72jL3UNzRmKPCOZ+KD
IxbGxHCtqAHMU8dBYZ5yQugW7mLnzmTAyFtfem9KuJXMFlpKICadOK6yZDYH2U3Y
SPGONS3vRZEhlfuDmyh0U18L7A3H25ELdbUTYhQLfZj0/OCy0cyqESzn65cpSPS8
/Fg2BdnmyzkWqo45KF5l4KCoT3+Jd2BHC6uZ8na/LpW6WncQ43+8obBbthgHODLA
rHGTTA7ccR+2aWFHjlZaGHErj7mjdRiNuOzlpWVpfMwcvMoCxN71DUxrVIdQchY4
j7K4fvt5ZpR3Ln0pFd9QgbwES6bqi2U0MZ872ChQ/LPoYhGxqrsEpJTJoZBnJ7Ik
IL4MiHLcN+9RKwamBhC9qjOp9V1IHVexxPvRxWc1Ix3Vwp5W7hFSZoM+pBruMjsF
9gc8pEL3RETBomr8zSGRPKDAvHv/V+r31pHTEr9pgEn3rN7wU7VCABVrK7pHJltl
03nm5LAu7HdLxFYysxur
=6o0j
-----END PGP SIGNATURE-----
Now we want to verify the authenticity of the downloaded file. The tool that we're going to use is gpg. gpg is included in Linux distributions (I am using Ubuntu).
$ gpg --help
gpg (GnuPG) 2.2.19
libgcrypt 1.8.5
Copyright (C) 2019 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <https://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.
Home: /home/bojan/.gnupg
Supported algorithms:
Pubkey: RSA, ELG, DSA, ECDH, ECDSA, EDDSA
Cypher: IDEA, 3DES, CAST5, BLOWFISH, AES, AES192, AES256, TWOFISH,
CAMELLIA128, CAMELLIA192, CAMELLIA256
Hash: SHA1, RIPEMD160, SHA256, SHA384, SHA512, SHA224
Compression: Uncompressed, ZIP, ZLIB, BZIP2
Syntax: gpg [options] [files]
Sign, check, encrypt or decrypt
Default operation depends on the input data
Commands:
-s, --sign make a signature
--clear-sign make a clear text signature
-b, --detach-sign make a detached signature
-e, --encrypt encrypt data
-c, --symmetric encryption only with symmetric cypher
-d, --decrypt decrypt data (default)
--verify verify a signature
-k, --list-keys list keys
--list-signatures list keys and signatures
--check-signatures list and check key signatures
--fingerprint list keys and fingerprints
-K, --list-secret-keys list secret keys
--generate-key generate a new key pair
--quick-generate-key quickly generate a new key pair
--quick-add-uid quickly add a new user-id
--quick-revoke-uid quickly revoke a user-id
--quick-set-expire quickly set a new expiration date
--full-generate-key full featured key pair generation
--generate-revocation generate a revocation certificate
--delete-keys remove keys from the public keyring
--delete-secret-keys remove keys from the secret keyring
--quick-sign-key quickly sign a key
--quick-lsign-key quickly sign a key locally
--sign-key sign a key
--lsign-key sign a key locally
--edit-key sign or edit a key
--change-passphrase change a passphrase
--export export keys
--send-keys export keys to a keyserver
--receive-keys import keys from a keyserver
--search-keys search for keys on a keyserver
--refresh-keys update all keys from a keyserver
--import import/merge keys
--card-status print the card status
--edit-card change data on a card
--change-pin change a card's PIN
--update-trustdb update the trust database
--print-md print message digests
--server run in server mode
--tofu-policy VALUE set the TOFU policy for a key
Options:
-a, --armor create ASCII armoured output
-r, --recipient USER-ID encrypt for USER-ID
-u, --local-user USER-ID use USER-ID to sign or decrypt
-z N set compress level to N (0 disables)
--textmode use canonical text mode
-o, --output FILE write output to FILE
-v, --verbose verbose
-n, --dry-run do not make any changes
-i, --interactive prompt before overwriting
--openpgp use strict OpenPGP behaviour
(See the man page for a complete listing of all commands and options)
Examples:
-se -r Bob [file] sign and encrypt for user Bob
--clear-sign [file] make a clear text signature
--detach-sign [file] make a detached signature
--list-keys [names] show keys
--fingerprint [names] show fingerprints
Please report bugs to <https://bugs.gnupg.org>.
From Tor's website we can find that it is signed by Tor Browser Developers and that their signing key is 0xEF6E286DDA85EA2A4BA7DE684E2C6E8793298290.
gpg can import the public GPG (build) key of the author (which is usually named after author's email) from Web Key Directory it deducts from author's public email ID:
$ gpg --auto-key-locate nodefault,wkd --locate-keys torbrowser@torproject.org
gpg: key 4E2C6E8793298290: public key "Tor Browser Developers (signing key) <torbrowser@torproject.org>" imported
gpg: Total number processed: 1
gpg: imported: 1
pub rsa4096 2014-12-15 [C] [expires: 2025-07-21]
EF6E286DDA85EA2A4BA7DE684E2C6E8793298290
uid [ unknown] Tor Browser Developers (signing key) <torbrowser@torproject.org>
sub rsa4096 2018-05-26 [S] [expires: 2021-06-12]
--auto-key-locate defines how will gpg locate keys:
--auto-key-locate mechanisms
--no-auto-key-locate
GnuPG can automatically locate and retrieve keys as needed using this option. This happens when encrypting to an email address (in the "user@example.com" form), and there are no "user@example.com" keys on the local keyring. This option takes any number of the mechanisms listed below, in the order they are to be tried. Instead of listing the mechanisms as comma delimited arguments, the option may also be given several times to add more mechanism. The option --no-auto-key-locate or the mechanism "clear" resets the list. The default is "local,wkd".
cert Locate a key using DNS CERT, as specified in RFC-4398.
pka Locate a key using DNS PKA.
dane Locate a key using DANE, as specified in draft-ietf-dane-openpgpkey-05.txt.
wkd Locate a key using the Web Key Directory protocol.
ldap Using DNS Service Discovery, check the domain in question for any LDAP keyservers to use. If this fails, attempt to locate the key using the PGP Universal method of checking ‘ldap://keys.(thedomain)’.
keyserver Locate a key using a keyserver.
keyserver-URL In addition, a keyserver URL as used in the dirmngr configuration may be used here to query that particular keyserver.
local Locate the key using the local keyrings. This mechanism allows the user to select the order a local key lookup is done. Thus using ‘--auto-key-locate local’ is identical to --no-auto-key-locate.
nodefault
This flag disables the standard local key lookup, done before any of the mechanisms defined by the --auto-key-locate are tried. The position of this mechanism in the list does not matter. It is not required if local is also used.
clear Clear all defined mechanisms. This is useful to override mechanisms given in a config file. Note that a nodefault in mechanisms will also be cleared unless it is given after the clear.
Web Key Directories:
- provide an easy way to discover public keys through HTTPS
- way to retrieve PGP keys without using a keyserver
- keep email addresses private (as you need to know an address already to ask for a public key)
- are authoritative source for a public key for its domain
Web Key Directory protocol:
- The sender's mail client checks a "well known" URL on the domain of the recipient.
- If a public key is available for that mail address, will be downloaded via HTTPS.
- The downloaded pubkey can now be used without further user interaction.
How does gpg find the URL of the key? It uses the host name from the provided email address and constructs it. See here: Attacks on GnuPG's Web Key Directory (WKD) | SektionEins GmbH and here: draft-koch-openpgp-webkey-service-08 - OpenPGP Web Key Directory.
torbrowser@torproject.org =>
username (local-part): torbrowser
domain: torproject.org
$ echo -n "torbrowser" | sha1sum
5426242bb720dfdd6dc01bfcddfe3f5b92d9f065
I used Cryptii online tool which uses z-base-32 Encoder to encrypt the string (sha1 sum):
kounek7zrdx745qydx6p59t9mqjpuhdf
We now have zbase32 username.
Every domain can host host the WKD from a special openpgpkey subdomain: openpgpkey.
It should have a folder structure similar to this:
.well-known
.well-known/openpgpkey
.well-known/openpgpkey/example.com
.well-known/openpgpkey/example.com/policy
.well-known/openpgpkey/example.com/hu
.well-known/openpgpkey/example.com/hu/<zbase32_username>
In our case:
https://openpgpkey.torproject.org/.well-known/openpgpkey/torproject.org/hu/kounek7zrdx745qydx6p59t9mqjpuhdf
We can see that Tor Browser Developers (signing key) is at that URL (I'm printing here only first 4 lines of the output):
$ curl https://openpgpkey.torproject.org/.well-known/openpgpkey/hu/kounek7zrdx745qydx6p59t9mqjpuhdf?l=torbrowser \
--output - \
| head -n4
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
T����2ϐ��,�c݉0� 0 0 0 0 0 --:--:-- --:--:-- --:--:-- 0
� Jʸ�X�jV_���>�P�AD���2B�>[���Ԟrv�K
Ge�ӧGWo��31�S#�2�H�+Յ5�Y�8{�Պbq'M�����D\l}�~9�S!/�v(�w�(;���bX/���@Tor Browser Developers (signing key) <torbrowser@torproject.org>�Te%�G�'��`;��X�;D�5q_���¤�f=��N��)�>�'�n}��K��TGhi��7;����[�
>
5025 0 0 14075 0 --:--:-- --:--:-- --:--:-- 14036
(23) Failed writing body
Let's save this key in tor.keyring file:
$ gpg --output ./tor.keyring --export 0xEF6E286DDA85EA2A4BA7DE684E2C6E8793298290
Verification of the signature is done via gpgv tool:
$ gpgv --help
gpgv (GnuPG) 2.2.19
libgcrypt 1.8.5
Copyright (C) 2019 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <https://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.
Syntax: gpgv [options] [files]
Check signatures against known trusted keys
Options:
-v, --verbose verbose
-q, --quiet be somewhat more quiet
--keyring FILE take the keys from the keyring FILE
-o, --output FILE write output to FILE
--ignore-time-conflict make timestamp conflicts only a warning
--status-fd FD write status info to this FD
--weak-digest ALGO reject signatures made with ALGO
Please report bugs to <https://bugs.gnupg.org>.
$ gpgv \
--keyring ./tor.keyring \
~/Downloads/tor-browser-linux64-10.0.10_en-US.tar.xz.asc \ ~/Downloads/tor-browser-linux64-10.0.10_en-US.tar.xz
gpgv: Signature made Thu 04 Feb 2021 06:28:43 GMT
gpgv: using RSA key EB774491D9FF06E2
gpgv: Good signature from "Tor Browser Developers (signing key) <torbrowser@torproject.org>"
What if author does not support Web Key Directory?
This is the case with e.g. Oracle. MySQL :: MySQL 8.0 Reference Manual :: 2.1.4.2 Signature Checking Using GnuPG states that the public build GPG key name is mysql-build@oss.oracle.com but WKD search fails:
$ gpg --auto-key-locate nodefault,wkd --locate-keys mysql-build@oss.oracle.com
gpg: error retrieving 'mysql-build@oss.oracle.com' via WKD: No data
gpg: error reading key: No data
In this case the author might provide (on their website) the public key which can be downloaded or copy-pasted into a locally stored file. Oracle provides this key in this way and we can save it in a file named e.g. mysql_pubkey.pub:
$ cat mysql_pubkey.pub
-----BEGIN PGP PUBLIC KEY BLOCK-----
Version: GnuPG v1
mQGiBD4+owwRBAC14GIfUfCyEDSIePvEW3SAFUdJBtoQHH/nJKZyQT7h9bPlUWC3
RODjQReyCITRrdwyrKUGku2FmeVGwn2u2WmDMNABLnpprWPkBdCk96+OmSLN9brZ
fw2vOUgCmYv2hW0hyDHuvYlQA/BThQoADgj8AW6/0Lo7V1W9/8VuHP0gQwCgvzV3
BqOxRznNCRCRxAuAuVztHRcEAJooQK1+iSiunZMYD1WufeXfshc57S/+yeJkegNW
...
roUTAKClYAhZuX2nUNwH4vlEJQHDqYa5yQ==
=ghXk
-----END PGP PUBLIC KEY BLOCK-----
Before importing this public key, we want to verify that it hasn't been tampered on its way from issuer to us. The file author/issuer usually displays (on the file download page) the public key fingerprint (thumbrint) which is basically a unique, shortened version (hash) of the full key. gpg tool can extract the fingerprint from the public key without importing it.
From the man gpg:
--show-keysThis commands takes OpenPGP keys as input and prints informationabout them in the same way the command --list-keys does for lo‐cally stored key. In addition the list options show-unusable-uids, show-unusable-subkeys, show-notations and show-policy-urlsare also enabled. As usual for automated processing, this com‐mand should be combined with the option --with-colons.
--fingerprintList all keys (or the specified ones) along with their finger‐prints. This is the same output as --list-keys but with the ad‐ditional output of a line with the fingerprint. May also be com‐bined with --check-signatures. If this command is given twice,the fingerprints of all secondary keys are listed too. Thiscommand also forces pretty printing of fingerprints if the keyidformat has been set to "none".
--show-keys is used for not-yet-imported keys while --fingerprint is used for keys already imported in the local keyring.
The fingerprint in the output of
$ gpg --show-keys package-signing-key.pub
pub rsa4096 2022-08-18 [SC]
59C86188E22ABB19BD5540477B04A1B8DD79B481
uid Zoom Video Communications, Inc. <CryptoOpsCodeSignProd@zoom.us>
sub rsa2048 2022-08-18 [A]
sub rsa2048 2022-08-18 [E]
...should match the fingerprint shown on the file's download page. This example was for Zoom's rpm packages.
Note that we can pass the public key string instead of file to gpg:
$ gpg --fingerprint <<EOT
-----BEGIN PGP PUBLIC KEY BLOCK-----
AwIDFQIDAxYCAQIeAQIXgAAKCRCf8lmA9bp+T/G/AKDM1QDs7il/CJhTycgDvE3c
cmlubyA8aGlyYW1AaGlyYW1jaGlyaW5vLmNvbT6IWwQTEQIAGwUCQ+ylKwYLCQgH
...
=RBPl
-----END PGP PUBLIC KEY BLOCK-----
EOT
Now we can import this public key to our local public GPG keyring:
$ gpg --import mysql_pubkey.pub
gpg: key 8C718D3B5072E1F5: 75 signatures not checked due to missing keys
gpg: key 8C718D3B5072E1F5: public key "MySQL Release Engineering <mysql-build@oss.oracle.com>" imported
gpg: Total number processed: 1
gpg: imported: 1
gpg: no ultimately trusted keys found
This keyring is stored in ~/.gnupg/ directory (which can be copied to another machine):
$ ls ~/.gnupg/
crls.d private-keys-v1.d pubring.kbx pubring.kbx~ trustdb.gpg
We can list all currently added public keys in it:
$ gpg --list-keys
/home/bojan/.gnupg/pubring.kbx
------------------------------
pub rsa4096 2014-12-15 [C] [expires: 2025-07-21]
EF6E286DDA85EA2A4BA7DE684E2C6E8793298290
uid [ unknown] Tor Browser Developers (signing key) <torbrowser@torproject.org>
sub rsa4096 2018-05-26 [S] [expires: 2021-06-12]
pub dsa1024 2003-02-03 [SCA] [expires: 2022-02-16]
A4A9406876FCBD3C456770C88C718D3B5072E1F5
uid [ unknown] MySQL Release Engineering <mysql-build@oss.oracle.com>
After importing Zoom's key:
$ gpg --fingerprint
/home/bojan/.gnupg/pubring.kbx
------------------------------
pub rsa4096 2014-12-15 [C] [expires: 2025-07-21]
EF6E 286D DA85 EA2A 4BA7 DE68 4E2C 6E87 9329 8290
uid [ unknown] Tor Browser Developers (signing key) <torbrowser@torproject.org>
pub dsa1024 2003-02-03 [SCA] [expired: 2022-02-16]
A4A9 4068 76FC BD3C 4567 70C8 8C71 8D3B 5072 E1F5
uid [ expired] MySQL Release Engineering <mysql-build@oss.oracle.com>
pub rsa4096 2022-08-18 [SC]
59C8 6188 E22A BB19 BD55 4047 7B04 A1B8 DD79 B481
uid [ unknown] Zoom Video Communications, Inc. <CryptoOpsCodeSignProd@zoom.us>
sub rsa2048 2022-08-18 [A]
sub rsa2048 2022-08-18 [E]
I have both an Oracle's binary file and its GPG key in the same directory:
$ ls mysql-workbench-community_8.0.23-1ubuntu20.04_amd64*
mysql-workbench-community_8.0.23-1ubuntu20.04_amd64.deb
mysql-workbench-community_8.0.23-1ubuntu20.04_amd64.deb.asc
...and can verify it now:
$ gpg --verify mysql-workbench-community_8.0.23-1ubuntu20.04_amd64.deb.asc
gpg: assuming signed data in 'mysql-workbench-community_8.0.23-1ubuntu20.04_amd64.deb'
gpg: Signature made Fri 18 Dec 2020 07:56:43 GMT
gpg: using DSA key A4A9406876FCBD3C456770C88C718D3B5072E1F5
gpg: issuer "mysql-build@oss.oracle.com"
gpg: Good signature from "MySQL Release Engineering <mysql-build@oss.oracle.com>" [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: A4A9 4068 76FC BD3C 4567 70C8 8C71 8D3B 5072 E1F5
Regarding this warning MySQL :: MySQL 8.0 Reference Manual :: 2.1.4.2 Signature Checking Using GnuPG states the following:
That is normal, as they depend on your setup and configuration. Here are explanations for these warnings:gpg: no ultimately trusted keys found: This means that the specific key is not "ultimately trusted" by you or your web of trust, which is okay for the purposes of verifying file signatures.WARNING: This key is not certified with a trusted signature! There is no indication that the signature belongs to the owner.: This refers to your level of trust in your belief that you possess our real public key. This is a personal decision. Ideally, a MySQL developer would hand you the key in person, but more commonly, you downloaded it. Was the download tampered with? Probably not, but this decision is up to you. Setting up a web of trust is one method for trusting them.
So the last warning actually tells us that the public PGP key might have been tampered/modified when we were copy/pasting it from the web page to our local file. Was web page authentic? Did we by any chance modified the content of the key in the mysql_pubkey.pub file before we imported it to our local keychain? Everything is possible but if we believe the original key has been imported, we can ignore this warning.
Fetching public GPG key by its ID
We showed above how to fetch key from WKD server if we know its name in the form of email address or if we have the content of the key. There is the third way how we can fetch the key, it's if we know its ID which might be published on the author's website:
We can download the key from the public keyserver using the public key id, 5072E1F5:
$ gpg --recv-keys 5072E1F5