Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Download key with `gpg --recv-key` and simultaneously check fingerprint in a script

How can I import a key via gpg --recv-key and check it's fingerprint automatically? Ideally, I would use gpg --recv-key $fingerprint directly, but gpg only recently added a check, that the received key(s) actually had the correct fingerprint instead of trusting the key-server blindly and the fix has not landed in all distributions I care about (e.g. the Docker Ubuntu Image still has an old gpg version).

I want to use it in combination with apt-key to add a PPA to a docker container.

like image 701
Perseids Avatar asked Sep 29 '22 17:09

Perseids


1 Answers

Solution:

#!/bin/bash
set -e
tempName=$(mktemp)
gpg --status-fd 1 --keyserver keyserver.ubuntu.com --recv-keys $fingerprint 1> $tempName 2>/dev/null
grep "^\[GNUPG\:\] IMPORT_OK "[[:digit:]]*" "$fingerprint"$" $tempName
grep "^\[GNUPG\:\] IMPORT_RES 1" $tempName

This script will return with a non-zero exit code if the key can't be downloaded or if the keyserver returns a malicious key. Beware: The malicious key still ends up in the gpg keyring, so you if you use it outside of a Dockerfile you will probably want to restore the original key ring afterwards. The commands for use in a Dockerfile (adding a rust PPA as an example):

RUN echo "deb http://ppa.launchpad.net/hansjorg/rust/ubuntu trusty main" >> /etc/apt/sources.list
RUN echo "deb-src http://ppa.launchpad.net/hansjorg/rust/ubuntu trusty main" >> /etc/apt/sources.list
RUN bash -c 'set -e;tempName=$(mktemp);apt-key adv --status-fd 1 --keyserver keyserver.ubuntu.com --recv-keys C03264CD6CADC10BFD6E708B37FD5E80BD6B6386 1> $tempName 2>/dev/null;grep "^\[GNUPG\:\] IMPORT_OK [[:digit:]]* C03264CD6CADC10BFD6E708B37FD5E80BD6B6386$" $tempName;grep "^\[GNUPG\:\] IMPORT_RES 1" $tempName'

Explanation:

The first building block to consider is GnuPGs --status-fd option. It tells gpg to write machine readable output to the given file descriptor. The file descriptor 1 always references stdout, so we will use that. Then we will have to find out what the output of --status-fd looks like. The documentation for that is not in the manpage, but in doc/DETAILS. An example output looks like this:

# gpg --status-fd 1 --keyserver keyserver.ubuntu.com --recv-keys BD6B6386 2>/dev/null
[GNUPG:] IMPORTED 37FD5E80BD6B6386 Launchpad PPA for Hans Jørgen Hoel
[GNUPG:] IMPORT_OK 1 C03264CD6CADC10BFD6E708B37FD5E80BD6B6386
[GNUPG:] IMPORT_RES 1 0 1 1 0 0 0 0 0 0 0 0 0 0

So we are looking for IMPORT_OK and IMPORT_RES lines. The second parameter after IMPORT_OK is the actual fingerprint of the imported key. The first parameter after IMPORT_RES is the number of keys imported.

In the output, gpg escapes newlines, so it is ok to match for lines beginning with [GNUPG:] to assert we not matching in strings that are controlled by an attacker (for example the name field in the key could otherwise contain \n[GNUPG:] IMPORT_OK 1 C03264CD6CADC10BFD6E708B37FD5E80BD6B6386] and fool us by creating a match).

With grep we can match for a line beginning with [GNUPG] sometext via grep "^\[GNUPG\:\]" and for a whole line with grep "^\[GNUPG\:\] sometext$" (^ and $ represent the start and end of a line). According to the documentation, any number following IMPORT_OK is ok for us, so we match against "[[:digit:]]*". Thus, as regular expressions we get "^\[GNUPG\:\] IMPORT_OK "[[:digit:]]*" "$fingerprint"$" and "^\[GNUPG\:\] IMPORT_RES 1"

As we want to match the output twice, we safe it into a temporary file (by creating an empty temporary file with mktemp and rerouting the output in that file). If grep does not match any lines it returns with a non-zero error code. We can use that by instructing bash to abort on any error via set -e. Overall we end up with:

set -e
tempName=$(mktemp)
gpg --status-fd 1 --keyserver keyserver.ubuntu.com --recv-keys $fingerprint 1> $tempName 2>/dev/null
grep "^\[GNUPG\:\] IMPORT_OK "[[:digit:]]*" "$fingerprint"$" $tempName
grep "^\[GNUPG\:\] IMPORT_RES 1" $tempName

How to use with apt to add a repository key: apt-key features the adv command to hand over command line parameters directly to gpg (run the above command without output rerouting to see the actual gpg command generated by apt-key). So we can simply exchange gpg with apt-key adv to operate on the repository key ring.

like image 74
Perseids Avatar answered Oct 05 '22 06:10

Perseids