From 25f2731928f0b571f7521d7d7a7e301499d0f6ee Mon Sep 17 00:00:00 2001 From: David Kalnischkies Date: Tue, 7 Jul 2015 11:46:39 +0200 Subject: [PATCH] merge keyrings with cat instead of gpg in apt-key If all keyrings are simple keyrings we can merge the keyrings with cat rather than doing a detour over gpg --export | --import (see #790665), which means 'apt-key verify' can do without gpg and just use gpgv as before the merging change. We declare this gpgv usage explicit now in the dependencies. This isn't a new dependency as gnupg as well as debian-archive-keyring depend on and we used it before unconditionally, just that we didn't declare it. The handling of the merged keyring needs to be slightly different as our merged keyring can end up containing the same key multiple times, but at least currently gpg does remove only the first occurrence with --delete-keys, so we move the handling to a if one is gone, all are gone rather than an (implicit) quid pro quo or even no effect. Thanks: Daniel Kahn Gillmor for the suggestion --- cmdline/apt-key.in | 128 +++++++++++++++++++++++----------- debian/control | 2 +- test/integration/test-apt-key | 2 +- 3 files changed, 88 insertions(+), 44 deletions(-) diff --git a/cmdline/apt-key.in b/cmdline/apt-key.in index b15f71f6d..881f8a990 100644 --- a/cmdline/apt-key.in +++ b/cmdline/apt-key.in @@ -34,7 +34,8 @@ get_fingerprints_of_keyring() { elif [ "${fprline%%:*}" != 'fpr' ]; then continue; fi echo "$fprline" | cut -d':' -f 10 done - done + # order in the keyring shouldn't be important + done | sort } add_keys_with_verify_against_master_keyring() { @@ -167,8 +168,10 @@ remove_key_from_keyring() { local GPG="$GPG_CMD --keyring $KEYRINGFILE" for KEY in "$@"; do - # check if the key is in this keyring: the key id is in the 5 column at the end - if ! get_fingerprints_of_keyring "$KEYRINGFILE" | grep -iq "^[0-9A-F]*${KEY}$"; then + local FINGERPRINTS="${GPGHOMEDIR}/keyringfile.keylst" + get_fingerprints_of_keyring "$KEYRINGFILE" > "$FINGERPRINTS" + # check if the key is in this keyring + if ! grep -iq "^[0-9A-F]*${KEY}$" "$FINGERPRINTS"; then continue fi if [ ! -w "$KEYRINGFILE" ]; then @@ -176,7 +179,7 @@ remove_key_from_keyring() { continue fi # check if it is the only key in the keyring and if so remove the keyring altogether - if [ '1' = "$(get_fingerprints_of_keyring "$KEYRINGFILE" | wc -l)" ]; then + if [ '1' = "$(uniq "$FINGERPRINTS" | wc -l)" ]; then mv -f "$KEYRINGFILE" "${KEYRINGFILE}~" # behave like gpg return fi @@ -188,7 +191,7 @@ remove_key_from_keyring() { cp -a "$REALTARGET" "$KEYRINGFILE" fi # delete the key from the keyring - $GPG --batch --delete-key --yes "$KEY" + $GPG --batch --delete-keys --yes "$KEY" if [ -n "$REALTARGET" ]; then # the real backup is the old link, not the copy we made mv -f "${KEYRINGFILE}.dpkg-tmp" "${KEYRINGFILE}~" @@ -268,6 +271,33 @@ import_keyring_into_keyring() { fi } +merge_all_trusted_keyrings_into_pubring() { + # does the same as: + # foreach_keyring_do 'import_keys_from_keyring' "${GPGHOMEDIR}/pubring.gpg" + # but without using gpg, just cat and find + local PUBRING="${GPGHOMEDIR}/pubring.gpg" + # if a --keyring was given, just use this one + if [ -n "$FORCED_KEYRING" ]; then + if [ -s "$FORCED_KEYRING" ]; then + cp --dereference "$FORCED_KEYRING" "$PUBRING" + fi + else + # otherwise all known keyrings are merged + local TRUSTEDPARTS="/etc/apt/trusted.gpg.d" + eval $(apt-config shell TRUSTEDPARTS Dir::Etc::TrustedParts/d) + if [ -d "$TRUSTEDPARTS" ]; then + # ignore errors mostly for non-existing $TRUSTEDFILE + cat /dev/null "$TRUSTEDFILE" $(find -L "$TRUSTEDPARTS" -type f -name '*.gpg') > "$PUBRING" 2>/dev/null || true + elif [ -s "$TRUSTEDFILE" ]; then + cp --dereference "$TRUSTEDFILE" "$PUBRING" + fi + fi + + if [ ! -s "$PUBRING" ]; then + touch "$PUBRING" + fi +} + import_keys_from_keyring() { import_keyring_into_keyring "$1" "$2" } @@ -288,29 +318,29 @@ merge_back_changes() { # look for keys which were added or removed get_fingerprints_of_keyring "${GPGHOMEDIR}/pubring.orig.gpg" > "${GPGHOMEDIR}/pubring.orig.keylst" get_fingerprints_of_keyring "${GPGHOMEDIR}/pubring.gpg" > "${GPGHOMEDIR}/pubring.keylst" - sort "${GPGHOMEDIR}/pubring.keylst" "${GPGHOMEDIR}/pubring.orig.keylst" | uniq --unique | while read key; do - if grep -q "^${key}$" "${GPGHOMEDIR}/pubring.orig.keylst"; then - # key isn't part of new keyring, so remove - foreach_keyring_do 'remove_key_from_keyring' "$key" - elif grep -q "^${key}$" "${GPGHOMEDIR}/pubring.keylst"; then - # key is part of new keyring, so we need to import it - import_keyring_into_keyring '' "$TRUSTEDFILE" "$key" - else - echo >&2 "Errror: Key ${key} (dis)appeared out of nowhere" - fi + comm -3 "${GPGHOMEDIR}/pubring.keylst" "${GPGHOMEDIR}/pubring.orig.keylst" > "${GPGHOMEDIR}/pubring.diff" + # key isn't part of new keyring, so remove + cut -f 2 "${GPGHOMEDIR}/pubring.diff" | while read key; do + if [ -z "$key" ]; then continue; fi + foreach_keyring_do 'remove_key_from_keyring' "$key" + done + # key is only part of new keyring, so we need to import it + cut -f 1 "${GPGHOMEDIR}/pubring.diff" | while read key; do + if [ -z "$key" ]; then continue; fi + import_keyring_into_keyring '' "$TRUSTEDFILE" "$key" done } setup_merged_keyring() { if [ -n "$FORCED_KEYID" ]; then - foreach_keyring_do 'import_keys_from_keyring' "${GPGHOMEDIR}/pubring.gpg" + merge_all_trusted_keyrings_into_pubring FORCED_KEYRING="${GPGHOMEDIR}/forcedkeyid.gpg" TRUSTEDFILE="${FORCED_KEYRING}" GPG="$GPG --keyring $TRUSTEDFILE" # ignore error as this "just" means we haven't found the forced keyid and the keyring will be empty import_keyring_into_keyring '' "$TRUSTEDFILE" "$FORCED_KEYID" || true elif [ -z "$FORCED_KEYRING" ]; then - foreach_keyring_do 'import_keys_from_keyring' "${GPGHOMEDIR}/pubring.gpg" + merge_all_trusted_keyrings_into_pubring if [ -r "${GPGHOMEDIR}/pubring.gpg" ]; then cp -a "${GPGHOMEDIR}/pubring.gpg" "${GPGHOMEDIR}/pubring.orig.gpg" else @@ -407,7 +437,23 @@ if [ -z "$command" ]; then fi shift -if [ "$command" != "help" ]; then +create_gpg_home() { + # gpg needs (in different versions more or less) files to function correctly, + # so we give it its own homedir and generate some valid content for it later on + if [ -n "$TMPDIR" ]; then + # tmpdir is a directory and current user has rwx access to it + # same tests as in apt-pkg/contrib/fileutl.cc GetTempDir() + if [ ! -d "$TMPDIR" ] || [ ! -r "$TMPDIR" ] || [ ! -w "$TMPDIR" ] || [ ! -x "$TMPDIR" ]; then + unset TMPDIR + fi + fi + GPGHOMEDIR="$(mktemp -d)" + CURRENTTRAP="${CURRENTTRAP} rm -rf '${GPGHOMEDIR}';" + trap "${CURRENTTRAP}" 0 HUP INT QUIT ILL ABRT FPE SEGV PIPE TERM + chmod 700 "$GPGHOMEDIR" +} + +prepare_gpg_home() { eval $(apt-config shell GPG_EXE Apt::Key::gpgcommand) if [ -n "$GPG_EXE" ] && which "$GPG_EXE" >/dev/null 2>&1; then @@ -418,7 +464,7 @@ if [ "$command" != "help" ]; then GPG_EXE="gpg2" else echo >&2 "Error: gnupg or gnupg2 do not seem to be installed," - echo >&2 "Error: but apt-key requires gnupg or gnupg2 for operation." + echo >&2 "Error: but apt-key requires gnupg or gnupg2 for this operation." echo >&2 exit 255 fi @@ -428,19 +474,8 @@ if [ "$command" != "help" ]; then fi GPG_CMD="$GPG_EXE --ignore-time-conflict --no-options --no-default-keyring" - # gpg needs (in different versions more or less) files to function correctly, - # so we give it its own homedir and generate some valid content for it - if [ -n "$TMPDIR" ]; then - # tmpdir is a directory and current user has rwx access to it - # same tests as in apt-pkg/contrib/fileutl.cc GetTempDir() - if [ ! -d "$TMPDIR" ] || [ ! -r "$TMPDIR" ] || [ ! -w "$TMPDIR" ] || [ ! -x "$TMPDIR" ]; then - unset TMPDIR - fi - fi - GPGHOMEDIR="$(mktemp -d)" - CURRENTTRAP="${CURRENTTRAP} rm -rf '${GPGHOMEDIR}';" - trap "${CURRENTTRAP}" 0 HUP INT QUIT ILL ABRT FPE SEGV PIPE TERM - chmod 700 "$GPGHOMEDIR" + create_gpg_home + # We don't use a secret keyring, of course, but gpg panics and # implodes if there isn't one available - and writeable for imports SECRETKEYRING="${GPGHOMEDIR}/secring.gpg" @@ -466,6 +501,10 @@ if [ "$command" != "help" ]; then # anyhow, so nothing actually happens, but its three lines of output # nobody expects to see in apt-key context, so trigger it in silence echo -n | $GPG --batch --import >/dev/null 2>&1 || true +} + +if [ "$command" != 'help' ] && [ "$command" != 'verify' ]; then + prepare_gpg_home fi case "$command" in @@ -500,7 +539,7 @@ case "$command" in foreach_keyring_do 'run_cmd_on_keyring' --fingerprint "$@" ;; export|exportall) - foreach_keyring_do 'import_keys_from_keyring' "${GPGHOMEDIR}/pubring.gpg" + merge_all_trusted_keyrings_into_pubring $GPG_CMD --keyring "${GPGHOMEDIR}/pubring.gpg" --armor --export "$@" ;; adv*) @@ -510,21 +549,26 @@ case "$command" in merge_back_changes ;; verify) - setup_merged_keyring GPGV='' eval $(apt-config shell GPGV Apt::Key::gpgvcommand) - if [ -n "$GPGV" ] && ! which "$GPGV" >/dev/null 2>&1; then GPGV=''; + if [ -n "$GPGV" ] && which "$GPGV" >/dev/null 2>&1; then true; elif which gpgv >/dev/null 2>&1; then GPGV='gpgv'; elif which gpgv2 >/dev/null 2>&1; then GPGV='gpgv2'; + else + echo >&2 'ERROR: gpgv or gpgv2 required for verification' + exit 29 fi - if [ -n "$GPGV" ]; then - if [ -n "$FORCED_KEYRING" ]; then - $GPGV --homedir "${GPGHOMEDIR}" --keyring "${FORCED_KEYRING}" --ignore-time-conflict "$@" - else - $GPGV --homedir "${GPGHOMEDIR}" --keyring "${GPGHOMEDIR}/pubring.gpg" --ignore-time-conflict "$@" - fi + # for a forced keyid we need gpg --export, so full wrapping required + if [ -n "$FORCED_KEYID" ]; then + prepare_gpg_home + else + create_gpg_home + fi + setup_merged_keyring + if [ -n "$FORCED_KEYRING" ]; then + $GPGV --homedir "${GPGHOMEDIR}" --keyring "${FORCED_KEYRING}" --ignore-time-conflict "$@" else - $GPG --verify "$@" + $GPGV --homedir "${GPGHOMEDIR}" --keyring "${GPGHOMEDIR}/pubring.gpg" --ignore-time-conflict "$@" fi ;; help) diff --git a/debian/control b/debian/control index e71cb5103..7e2db7373 100644 --- a/debian/control +++ b/debian/control @@ -18,7 +18,7 @@ XS-Testsuite: autopkgtest Package: apt Architecture: any -Depends: ${shlibs:Depends}, ${misc:Depends}, ${apt:keyring}, gnupg | gnupg2, adduser +Depends: ${shlibs:Depends}, ${misc:Depends}, ${apt:keyring}, gpgv | gpgv2, gnupg | gnupg2, adduser Replaces: manpages-pl (<< 20060617-3~), manpages-it (<< 2.80-4~), sun-java6-jdk (>> 0), sun-java5-jdk (>> 0), openjdk-6-jdk (<< 6b24-1.11-0ubuntu1~) Breaks: manpages-pl (<< 20060617-3~), manpages-it (<< 2.80-4~), sun-java6-jdk (>> 0), sun-java5-jdk (>> 0), openjdk-6-jdk (<< 6b24-1.11-0ubuntu1~) Conflicts: python-apt (<< 0.7.93.2~) diff --git a/test/integration/test-apt-key b/test/integration/test-apt-key index 4dbf3d66d..1226e7dc4 100755 --- a/test/integration/test-apt-key +++ b/test/integration/test-apt-key @@ -188,7 +188,7 @@ gpg: unchanged: 1' aptkey --fakeroot update adv --batch --yes --default-key 'Marvin' --armor --detach-sign --sign --output signature.gpg signature - for GPGV in 'gpgv' 'gpgv2' '/does/not/exist'; do + for GPGV in '' 'gpgv' 'gpgv2'; do echo "APT::Key::GPGVCommand \"$GPGV\";" > rootdir/etc/apt/apt.conf.d/00gpgvcmd msgtest 'Test verify a file' 'with all keys' -- 2.45.2