Просмотр исходного кода

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
David Kalnischkies лет назад: 11
Родитель
Сommit
25f2731928
3 измененных файлов с 88 добавлено и 44 удалено
  1. 86 42
      cmdline/apt-key.in
  2. 1 1
      debian/control
  3. 1 1
      test/integration/test-apt-key

+ 86 - 42
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)

+ 1 - 1
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~)

+ 1 - 1
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'