Load the key with `gpg -recv-key` and check the fingerprint in the script at the same time

How to import a key through gpg --recv-key

and automatically check its fingerprint? Ideally I would use gpg --recv-key $fingerprint

directly, but gpg only recently added a check that the key (s) received actually had the correct fingerprint instead of blindly trusting the key server, and the fix did not land on all my distributions (e.g. Docker Ubuntu Image still has the old gpg version).

I want to use it in combination with apt-key

to add PPA to docker container.

+3


source to share


2 answers


Decision:

#!/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 cannot be loaded or the key server returns a malicious key. Beware: the malicious key still ends up in gpg-keyring, so if you're using it outside of the Dockerfile, you probably want to restore the original keyring afterwards. Commands to use in Dockerfile (adding 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 the GnuPGs option --status-fd

. It tells gpg to write machine-readable output to the specified file descriptor. The file descriptor 1

always refers to stdout, so we will use that. Then we will need to find out what the output looks like --status-fd

. The documentation for this is not in the man page, but in doc/DETAILS

. Sample 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're looking for strings IMPORT_OK

and IMPORT_RES

. The second parameter after IMPORT_OK

is the actual fingerprint of the imported key. The first parameter after IMPORT_RES

is the number of imported keys.

In the output, gpg accelerates newlines , so it is ok for lines starting with [GNUPG:]

to assert that we are not matching strings that are controlled by an attacker (for example, a name field in a key might contain \n[GNUPG:] IMPORT_OK 1 C03264CD6CADC10BFD6E708B37FD5E80BD6B6386]

and trick us into creating a match).

With grep we can match the line starting with [GNUPG] sometext

thru grep "^\[GNUPG\:\]"

, and for the whole line with grep "^\[GNUPG\:\] sometext$"

( ^

and $

represent the beginning and end of the line). According to the documentation, any number following IMPORT_OK

is fine for us, so we match with "[[: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 store it in a temporary file (creating an empty temporary file with mktemp

and redirecting the output to that file). If it grep

doesn't match the strings, it returns with a nonzero error code. We can exploit this by instructing bash

to abort any error with set -e

. In general, we get:

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

contains a command adv

to pass command line parameters directly to gpg (run the above command without redirecting the output to see the actual gpg command generated with apt-key

). Therefore, we can simply exchange gpg

with apt-key adv

to work with the repository key ring.

+3


source


Actually I came across this page looking for a solution to a problem similar to the one described by the OP. The mentioned solution gpg --recv-key $fingerprint

is fine and should be supported by gpg on most common distributions. But in my case, I had a different limitation. Gpg communications have been moved to a separate package dirmngr

some time ago. The package is not included in the default Ubuntu install starting with Yakkety Yak (i.e. this error ) and you have to install it manually to get it done on the team work.I tried to avoid this while my Docker image is a very minimal Ubuntu setup ... So I found some alternative solution that might be helpful. Namely, I install the Nginx server from the repositories hosted on nginx.org like this:

RUN set -Eeuxo pipefail; \
    # The keyring is placed in temporary directory
    export GNUPGHOME="$(mktemp -d)"; \
    # Nginx public key (used for signing packages and repositories)
    NGINX_GPGKEY=0x573BFD6B3D8FBC641079A6ABABF5BD827BD9BF62; \
    # Pool of high-available keyservers
    KEYSERVER=ha.pool.sks-keyservers.net:11371; \
    # HKP protocol can be easily represented as HTTP query. The key is imported into temporary keyring.
    curl -LfSs "http://$KEYSERVER/pks/lookup?op=get&search=$NGINX_GPGKEY&options=mr&exact=on" | gpg --import -; \
    # Additional check that imported key is the right one before copying it to apt trusted database
    gpg --export "$NGINX_GPGKEY" | apt-key --keyring /etc/apt/trusted.gpg.d/nginx.gpg add -; \
    # Adding nginx.org repository to the sources list
    echo "deb [arch=amd64] http://nginx.org/packages/mainline/ubuntu/ $(. /etc/lsb-release; echo $DISTRIB_CODENAME) nginx" > /etc/apt/sources.list.d/nginx.list; \
    # Installing Nginx
    apt-get update; \
    apt-get install -y --no-install-recommends --no-install-suggests nginx; \
    # Removing apt cached files
    apt-get clean; \
    rm -rf /var/lib/apt/lists/*; \
    # Removing temporary keyring
    rm -rf "$GNUPGHOME"

      



The code is an excerpt from the Dockerfile to run in the remove RUN

part shell and all comments (lines starting with #

). Also, if not done inside the Dockerfile, the export GNUPGHOME

may interfere with normal gpg behavior. To avoid this, either run the entire command in a sub-shell - wrap with a parenthesis (command)

, or run it after that - unset GNUPGHOME

. So the key is retrieved from the keyserver using curl (a simple simple HTTP request and certain URL parameters are used to emulate the HKP protocol) and imported into a temporary keyring. Then, by exporting it using a specific KEYID parameter, we check the key ID in case the keyserver response has changed. The key is imported into a separate apt keyring/etc/apt/trusted.gpg.d/nginx.gpg

so that it can be easily removed later by deleting the file. There is really no need to use apt-key

, you can instead redirect the gpg --export

output to a file:gpg --export "$NGINX_GPGKEY" > /etc/apt/trusted.gpg.d/nginx.gpg

The commands are perfectly self-explanatory, just some additional notes: the description of the HKP protocol can be found here , also there is a good explanation of why to use the initial one set

.

0


source







All Articles