2

I have:

  1. A file
  2. A detached PGP signature of that file in ASCII armor format and
  3. A 40-character (long-format) fingerprint identifying the one key that must have a valid signature

What is the correct way to write a BASH script to verify that the given signature is valid (only for the given fingerprint) for the given file using the gpg command on *nix?

Note: The solution ideally would not just parse STDOUT from gpg--such that the BASH script in the solution provided wouldn't break if the words or format of the output are slightly changed in the future.

And, especially important, detached signatures can be signed by multiple keys. So this solution should fail if, for example, an attacker took the file and its detached signature and edited the file while adding their own signature to the detached signature. Note that, with this attack, there would be a BAD signature present from the key whose fingerprint we're pinning in our script and a GOOD signature from the attacker's key, which is irrelevant. In this case, the solution must fail.

For example, consider the following:

  1. https://files.pythonhosted.org/packages/cb/85/8a1588a04172e0853352ecfe214264c65a62ab35374d9ad9c569cf94c2a3/python_gnupg-0.4.6-py2.py3-none-any.whl
  2. https://files.pythonhosted.org/packages/cb/85/8a1588a04172e0853352ecfe214264c65a62ab35374d9ad9c569cf94c2a3/python_gnupg-0.4.6-py2.py3-none-any.whl.asc
  3. CA749061914EAC138E66EADB9147B477339A9B86

Currently I have the following in my BASH script

#!/bin/bash

ONLY_TRUST_THIS_FINGERPRINT='CA749061914EAC138E66EADB9147B477339A9B86'

tmpDir="`mktemp -d`" || exit 1
pushd "${tmpDir}"

wget https://files.pythonhosted.org/packages/cb/85/8a1588a04172e0853352ecfe214264c65a62ab35374d9ad9c569cf94c2a3/python_gnupg-0.4.6-py2.py3-none-any.whl
wget https://files.pythonhosted.org/packages/cb/85/8a1588a04172e0853352ecfe214264c65a62ab35374d9ad9c569cf94c2a3/python_gnupg-0.4.6-py2.py3-none-any.whl.asc
wget https://keys.openpgp.org/vks/v1/by-fingerprint/CA749061914EAC138E66EADB9147B477339A9B86

mkdir gnupg
gpg --homedir "${tmpDir}/gnupg" --import CA749061914EAC138E66EADB9147B477339A9B86

What command(s) should follow in the script above to safely confirm that the file has a valid signature from the private key matching our pinned fingerprint?

EDIT: Here's an example output of a simple gpg --verify ... that has a GOOD signature by an attacker and a BAD signature from the actual developer; it should fail.

user@disp2952:/tmp/tmp.nUmxfwbwfK$ gpg --homedir gnupg/ --verify python_gnupg-0.4.6-py2.py3-none-any.whl.asc
gpg: WARNING: unsafe permissions on homedir '/tmp/tmp.nUmxfwbwfK/gnupg'
gpg: assuming signed data in 'python_gnupg-0.4.6-py2.py3-none-any.whl'
gpg: Signature made Sat 29 Aug 2020 10:04:03 PM +0545
gpg:                using RSA key 2DA3BAD0DB41087CA7E5E4C1F93C17B957F73F5A
gpg: Good signature from "Mallory <[email protected]>" [unknown]
gpg: Signature made Fri 17 Apr 2020 07:54:23 PM +0545
gpg:                using RSA key 9147B477339A9B86
gpg: BAD signature from "Vinay Sajip (CODE SIGNING KEY) <[email protected]>" [unknown]
user@disp2952:/tmp/tmp.nUmxfwbwfK$ echo $?
1
user@disp2952:/tmp/tmp.nUmxfwbwfK$ 

1 Answer 1

2

Use gpgv:

gpgv --homedir "${tmpDir}/gnupg" --keyring "${tmpDir}/gnupg/pubring.kbx" python_gnupg-0.4.6-py2.py3-none-any.whl.asc python_gnupg-0.4.6-py2.py3-none-any.whl

It will only indicate success (exit code 0) if all signatures are verified using keys in the given keyring.

If you want to check that the key you imported matched the fingerprint you asked for, you can ask gpg to list the keys in the keyring that match the fingerprint:

gpg --homedir "${tmpDir}/gnupg" --no-default-keyring --keyring "${tmpDir}/gnupg/pubring.kbx" --list-keys "${ONLY_TRUST_THIS_FINGERPRINT}"

This will indicate success if the key exists in the keyring, failure otherwise.

If you want to improve the trust checking, you should really store the downloaded key, and always used the stored key to verify the downloads, instead of re-downloading the key every time. This is the approach used in some distributions (Debian in particular) to validate new upstream releases: the known-good key is stored in the package source (in Debian, not upstream), and new releases are only validated if they are signed by the known-good key.

3
  • Confirmed. It looks like it exits 2 when there's no key in the keyring, 1 if there's any BAD signatures, and 0 if all the signatures are GOOD and in the keyring. So this works if using a temporary gnupg --home dir containing only the one public key that we're pinning. Commented Aug 29, 2020 at 17:22
  • For this solution to be valid, however, I think an additional command would be needed to confirm that the public key downloaded (and imported into our temp gnupg --homedir) from the key server matches our fingerpritnt. Commented Aug 29, 2020 at 17:27
  • Ah, on the assumption that the server might have returned a key not matching the fingerprint you requested? Commented Aug 29, 2020 at 17:43

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .