set -e
unset GREP_OPTIONS
-GPG_CMD="gpg --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
-GPGHOMEDIR="$(mktemp -d)"
-CURRENTTRAP="${CURRENTTRAP} rm -rf '${GPGHOMEDIR}';"
-trap "${CURRENTTRAP}" 0 HUP INT QUIT ILL ABRT FPE SEGV PIPE TERM
-chmod 700 "$GPGHOMEDIR"
-# 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"
-touch $SECRETKEYRING
-GPG_CMD="$GPG_CMD --homedir $GPGHOMEDIR"
-# create the trustdb with an (empty) dummy keyring
-# older gpgs required it, newer gpgs even warn that it isn't needed,
-# but require it nonetheless for some commands, so we just play safe
-# here for the foreseeable future and create a dummy one
-$GPG_CMD --quiet --check-trustdb --keyring $SECRETKEYRING >/dev/null 2>&1
-# tell gpg that it shouldn't try to maintain a trustdb file
-GPG_CMD="$GPG_CMD --no-auto-check-trustdb --trust-model always"
-GPG="$GPG_CMD"
-
APT_DIR="/"
eval $(apt-config shell APT_DIR Dir)
eval $(apt-config shell REMOVED_KEYS APT::Key::RemovedKeys)
ARCHIVE_KEYRING_URI='&keyring-uri;'
eval $(apt-config shell ARCHIVE_KEYRING_URI APT::Key::ArchiveKeyringURI)
-TMP_KEYRING=${APT_DIR}/var/lib/apt/keyrings/maybe-import-keyring.gpg
aptkey_echo() { echo "$@"; }
fi
}
+get_fingerprints_of_keyring() {
+ $GPG_CMD --keyring "$1" --with-colons --fingerprint | while read publine; do
+ # search for a public key
+ if [ "${publine%%:*}" != 'pub' ]; then continue; fi
+ # search for the associated fingerprint (should be the very next line)
+ while read fprline; do
+ if [ "${fprline%%:*}" = 'sub' ]; then break; # should never happen
+ elif [ "${fprline%%:*}" != 'fpr' ]; then continue; fi
+ echo "$fprline" | cut -d':' -f 10
+ done
+ done
+}
+
add_keys_with_verify_against_master_keyring() {
ADD_KEYRING=$1
MASTER=$2
# is honored. so:
# all keys that are exported must have a valid signature
# from a key in the $distro-master-keyring
- add_keys=`$GPG_CMD --keyring $ADD_KEYRING --with-colons --list-keys | grep ^pub | cut -d: -f5`
+ add_keys="$(get_fingerprints_of_keyring "$ADD_KEYRING")"
all_add_keys=`$GPG_CMD --keyring $ADD_KEYRING --with-colons --list-keys | grep ^[ps]ub | cut -d: -f5`
master_keys=`$GPG_CMD --keyring $MASTER --with-colons --list-keys | grep ^pub | cut -d: -f5`
fi
done
done
-
+
for add_key in $add_keys; do
# export the add keyring one-by-one
- rm -f $TMP_KEYRING
- $GPG_CMD --keyring $ADD_KEYRING --output $TMP_KEYRING --export $add_key
- # check if signed with the master key and only add in this case
- ADDED=0
+ local TMP_KEYRING="${GPGHOMEDIR}/tmp-keyring.gpg"
+ $GPG_CMD --batch --yes --keyring "$ADD_KEYRING" --output "$TMP_KEYRING" --export "$add_key"
+ if ! $GPG_CMD --batch --yes --keyring "$TMP_KEYRING" --import "$MASTER" > "${GPGHOMEDIR}/gpgoutput.log" 2>&1; then
+ cat "${GPGHOMEDIR}/gpgoutput.log"
+ false
+ fi
+ # check if signed with the master key and only add in this case
+ ADDED=0
for master_key in $master_keys; do
- if $GPG_CMD --keyring $MASTER --keyring $TMP_KEYRING --check-sigs --with-colons $add_key | grep '^sig:!:' | cut -d: -f5 | grep -q $master_key; then
- $GPG --import $TMP_KEYRING
+ if $GPG_CMD --keyring $TMP_KEYRING --check-sigs --with-colons $add_key | grep '^sig:!:' | cut -d: -f5 | grep -q $master_key; then
+ $GPG_CMD --batch --yes --keyring "$ADD_KEYRING" --export "$add_key" | $GPG --batch --yes --import
ADDED=1
fi
done
if [ $ADDED = 0 ]; then
echo >&2 "Key '$add_key' not added. It is not signed with a master key"
fi
+ rm -f "${TMP_KEYRING}"
done
- rm -f $TMP_KEYRING
}
# update the current archive signing keyring from a network URI
if [ -r "$REMOVED_KEYS" ]; then
# remove no-longer supported/used keys
- $GPG_CMD --keyring $REMOVED_KEYS --with-colons --list-keys | grep ^pub | cut -d: -f5 | while read key; do
+ get_fingerprints_of_keyring "$REMOVED_KEYS" | while read key; do
foreach_keyring_do 'remove_key_from_keyring' "$key"
done
else
fi
local GPG="$GPG_CMD --keyring $KEYRINGFILE"
- while [ -n "$1" ]; do
- local KEY="$1"
- shift
+ for KEY in "$@"; do
# check if the key is in this keyring: the key id is in the 5 column at the end
- if ! $GPG --with-colons --list-keys 2>&1 | grep -q "^pub:[^:]*:[^:]*:[^:]*:[0-9A-F]*${KEY}:"; then
+ if ! get_fingerprints_of_keyring "$KEYRINGFILE" | grep -q "^[0-9A-F]*${KEY}$"; then
continue
fi
if [ ! -w "$KEYRINGFILE" ]; then
continue
fi
# check if it is the only key in the keyring and if so remove the keyring altogether
- if [ '1' = "$($GPG --with-colons --list-keys | grep "^pub:[^:]*:[^:]*:[^:]*:[0-9A-F]\+:" | wc -l)" ]; then
+ if [ '1' = "$(get_fingerprints_of_keyring "$KEYRINGFILE" | wc -l)" ]; then
mv -f "$KEYRINGFILE" "${KEYRINGFILE}~" # behave like gpg
return
fi
fi
}
-list_keys_from_keyring() {
+run_cmd_on_keyring() {
local KEYRINGFILE="$1"
shift
- # don't show the error message if this keyring doesn't include the key
- $GPG_CMD --keyring "$KEYRINGFILE" --batch --list-keys "$@" 2>/dev/null || true
-}
-
-fingerprint_keys_from_keyring() {
- local KEYRINGFILE="$1"
- shift
- # don't show the error message if this keyring doesn't include the fingerprint
- $GPG_CMD --keyring "$KEYRINGFILE" --batch --fingerprint "$@" 2>/dev/null || true
+ # fingerprint and co will fail if key isn't in this keyring
+ $GPG_CMD --keyring "$KEYRINGFILE" --batch "$@" 2>/dev/null || true
}
import_keys_from_keyring() {
local IMPORT="$1"
local KEYRINGFILE="$2"
- $GPG_CMD --keyring "$KEYRINGFILE" --batch --import "$IMPORT" >/dev/null 2>&1
+ if ! $GPG_CMD --keyring "$KEYRINGFILE" --batch --import "$IMPORT" > "${GPGHOMEDIR}/gpgoutput.log" 2>&1; then
+ cat "${GPGHOMEDIR}/gpgoutput.log"
+ false
+ fi
}
-setup_merged_keyring() {
- local TRUSTEDFILE_BAK="$TRUSTEDFILE"
- TRUSTEDFILE='/dev/null'
- foreach_keyring_do 'import_keys_from_keyring' "${GPGHOMEDIR}/trusted.gpg"
- TRUSTEDFILE="$TRUSTEDFILE_BAK"
- # mark it as non-writeable so users get errors if gnupg tries to modify it
- if [ -s "${GPGHOMEDIR}/trusted.gpg" ]; then
- chmod -w "${GPGHOMEDIR}/trusted.gpg"
- GPG="$GPG --keyring ${GPGHOMEDIR}/trusted.gpg"
+merge_keys_into_keyrings() {
+ local KEYRINGFILE="$1"
+ local IMPORT="$2"
+ if ! $GPG_CMD --keyring "$KEYRINGFILE" --batch --import --import-options 'merge-only' "$IMPORT" > "${GPGHOMEDIR}/gpgoutput.log" 2>&1; then
+ cat "${GPGHOMEDIR}/gpgoutput.log"
+ false
+ fi
+}
+
+merge_back_changes() {
+ if [ -n "$FORCED_KEYRING" ]; then
+ # if the keyring was forced merge is already done
+ return
fi
- if [ -r "$TRUSTEDFILE" ]; then
- GPG="$GPG --keyring $TRUSTEDFILE --primary-keyring $TRUSTEDFILE"
+ if [ -s "${GPGHOMEDIR}/pubring.gpg" ]; then
+ # merge all updated keys
+ foreach_keyring_do 'merge_keys_into_keyrings' "${GPGHOMEDIR}/pubring.gpg"
fi
+ # 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
+ create_new_keyring "$TRUSTEDFILE"
+ if ! $GPG --batch --yes --export "$key" | $GPG_CMD --keyring "$TRUSTEDFILE" --batch --yes --import > "${GPGHOMEDIR}/gpgoutput.log" 2>&1; then
+ cat "${GPGHOMEDIR}/gpgoutput.log"
+ false
+ fi
+ else
+ echo >&2 "Errror: Key ${key} (dis)appeared out of nowhere"
+ fi
+ done
}
+setup_merged_keyring() {
+ if [ -z "$FORCED_KEYRING" ]; then
+ foreach_keyring_do 'import_keys_from_keyring' "${GPGHOMEDIR}/pubring.gpg"
+ if [ -r "${GPGHOMEDIR}/pubring.gpg" ]; then
+ cp -a "${GPGHOMEDIR}/pubring.gpg" "${GPGHOMEDIR}/pubring.orig.gpg"
+ else
+ touch "${GPGHOMEDIR}/pubring.gpg" "${GPGHOMEDIR}/pubring.orig.gpg"
+ fi
+ GPG="$GPG --keyring ${GPGHOMEDIR}/pubring.gpg"
+ else
+ GPG="$GPG --keyring $TRUSTEDFILE"
+ create_new_keyring "$TRUSTEDFILE"
+ fi
+}
+
+create_new_keyring() {
+ # gpg defaults to mode 0600 for new keyrings. Create one with 0644 instead.
+ if ! [ -e "$TRUSTEDFILE" ]; then
+ if [ -w "$(dirname "$TRUSTEDFILE")" ]; then
+ touch -- "$TRUSTEDFILE"
+ chmod 0644 -- "$TRUSTEDFILE"
+ fi
+ fi
+}
usage() {
echo "Usage: apt-key [--keyring file] [command] [arguments]"
shift
TRUSTEDFILE="$1"
FORCED_KEYRING="$1"
+ ;;
+ --secret-keyring)
shift
+ FORCED_SECRET_KEYRING="$1"
+ ;;
+ --readonly)
+ merge_back_changes() { true; }
;;
--fakeroot)
requires_root() { true; }
- shift
;;
--quiet)
aptkey_echo() { true; }
- shift
;;
--*)
echo >&2 "Unknown option: $1"
*)
break;;
esac
+ shift
done
if [ -z "$TRUSTEDFILE" ]; then
shift
if [ "$command" != "help" ]; then
- if ! which gpg >/dev/null 2>&1; then
- echo >&2 "Warning: gnupg does not seem to be installed."
- echo >&2 "Warning: apt-key requires gnupg for most operations."
+ eval $(apt-config shell GPG_EXE Apt::Key::gpgcommand)
+
+ if [ -n "$GPG_EXE" ] && which "$GPG_EXE" >/dev/null 2>&1; then
+ true
+ elif which gpg >/dev/null 2>&1; then
+ GPG_EXE="gpg"
+ elif which gpg2 >/dev/null 2>&1; 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
+ exit 255
fi
- # gpg defaults to mode 0600 for new keyrings. Create one with 0644 instead.
- if ! [ -e "$TRUSTEDFILE" ]; then
- if [ -w "$(dirname "$TRUSTEDFILE")" ]; then
- touch -- "$TRUSTEDFILE"
- chmod 0644 -- "$TRUSTEDFILE"
- 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"
+ # 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"
+ touch $SECRETKEYRING
+ GPG_CMD="$GPG_CMD --homedir $GPGHOMEDIR"
+ # create the trustdb with an (empty) dummy keyring
+ # older gpgs required it, newer gpgs even warn that it isn't needed,
+ # but require it nonetheless for some commands, so we just play safe
+ # here for the foreseeable future and create a dummy one
+ $GPG_CMD --quiet --check-trustdb --keyring $SECRETKEYRING >/dev/null 2>&1
+ # tell gpg that it shouldn't try to maintain a trustdb file
+ GPG_CMD="$GPG_CMD --no-auto-check-trustdb --trust-model always"
+ GPG="$GPG_CMD"
+
+ # for advanced operations, we might really need a secret keyring after all
+ if [ -n "$FORCED_SECRET_KEYRING" ] && [ -r "$FORCED_SECRET_KEYRING" ]; then
+ rm -f "$SECRETKEYRING"
+ cp -a "$FORCED_SECRET_KEYRING" "$SECRETKEYRING"
fi
fi
requires_root
setup_merged_keyring
$GPG --quiet --batch --import "$@"
+ merge_back_changes
aptkey_echo "OK"
;;
del|rm|remove)
requires_root
setup_merged_keyring
update
+ merge_back_changes
;;
net-update)
requires_root
setup_merged_keyring
net_update
+ merge_back_changes
;;
list)
- foreach_keyring_do 'list_keys_from_keyring' "$@"
+ foreach_keyring_do 'run_cmd_on_keyring' --list-keys "$@"
;;
finger*)
- foreach_keyring_do 'fingerprint_keys_from_keyring' "$@"
+ foreach_keyring_do 'run_cmd_on_keyring' --fingerprint "$@"
;;
export|exportall)
- foreach_keyring_do 'import_keys_from_keyring' "${GPGHOMEDIR}/trusted.gpg"
- $GPG_CMD --keyring "${GPGHOMEDIR}/trusted.gpg" --armor --export "$@"
+ foreach_keyring_do 'import_keys_from_keyring' "${GPGHOMEDIR}/pubring.gpg"
+ $GPG_CMD --keyring "${GPGHOMEDIR}/pubring.gpg" --armor --export "$@"
;;
adv*)
setup_merged_keyring
aptkey_echo "Executing: $GPG $*"
$GPG "$@"
+ merge_back_changes
+ ;;
+ verify)
+ setup_merged_keyring
+ if which gpgv >/dev/null 2>&1; then
+ gpgv --homedir "${GPGHOMEDIR}" --keyring "${GPGHOMEDIR}/pubring.gpg" --ignore-time-conflict "$@"
+ else
+ $GPG --verify "$@"
+ fi
;;
help)
usage