]> git.saurik.com Git - apt.git/blobdiff - cmdline/apt-key.in
Add missing includes and external definitions
[apt.git] / cmdline / apt-key.in
index c2e8d176ca3187661c7f66985ffec82bc5f1d84b..81314c7f5fab1a4d94fb67afae5ed804bffae12c 100644 (file)
@@ -4,17 +4,14 @@ set -e
 unset GREP_OPTIONS
 export IFS="$(printf "\n\b")"
 
-APT_DIR="/"
-eval $(apt-config shell APT_DIR Dir)
-
 MASTER_KEYRING='&keyring-master-filename;'
-eval $(apt-config shell MASTER_KEYRING APT::Key::MasterKeyring)
+eval "$(apt-config shell MASTER_KEYRING APT::Key::MasterKeyring)"
 ARCHIVE_KEYRING='&keyring-filename;'
-eval $(apt-config shell ARCHIVE_KEYRING APT::Key::ArchiveKeyring)
+eval "$(apt-config shell ARCHIVE_KEYRING APT::Key::ArchiveKeyring)"
 REMOVED_KEYS='&keyring-removed-filename;'
-eval $(apt-config shell REMOVED_KEYS APT::Key::RemovedKeys)
+eval "$(apt-config shell REMOVED_KEYS APT::Key::RemovedKeys)"
 ARCHIVE_KEYRING_URI='&keyring-uri;'
-eval $(apt-config shell ARCHIVE_KEYRING_URI APT::Key::ArchiveKeyringURI)
+eval "$(apt-config shell ARCHIVE_KEYRING_URI APT::Key::ArchiveKeyringURI)"
 
 aptkey_echo() { echo "$@"; }
 
@@ -25,6 +22,26 @@ requires_root() {
        fi
 }
 
+command_available() {
+    if [ -x "$1" ]; then return 0; fi
+    # command -v "$1" >/dev/null 2>&1 # not required by policy, see #747320
+    # which "$1" >/dev/null 2>&1 # is in debianutils (essential) but not on non-debian systems
+    local OLDIFS="$IFS"
+    IFS=:
+    for p in $PATH; do
+       if [ -x "${p}/${1}" ]; then
+           IFS="$OLDIFS"
+           return 0
+       fi
+    done
+    IFS="$OLDIFS"
+    return 1
+}
+
+escape_shell() {
+    echo "$@" | sed -e "s#'#'\"'\"'#g"
+}
+
 get_fingerprints_of_keyring() {
     aptkey_execute "$GPG_SH" --keyring "$1" --with-colons --fingerprint | while read publine; do
        # search for a public key
@@ -40,8 +57,8 @@ get_fingerprints_of_keyring() {
 }
 
 add_keys_with_verify_against_master_keyring() {
-    ADD_KEYRING=$1
-    MASTER=$2
+    ADD_KEYRING="$1"
+    MASTER="$2"
 
     if [ ! -f "$ADD_KEYRING" ]; then
        echo >&2 "ERROR: '$ADD_KEYRING' not found"
@@ -57,8 +74,8 @@ add_keys_with_verify_against_master_keyring() {
     #   all keys that are exported must have a valid signature
     #   from a key in the $distro-master-keyring
     add_keys="$(get_fingerprints_of_keyring "$ADD_KEYRING")"
-    all_add_keys=`aptkey_execute "$GPG_SH" --keyring "$ADD_KEYRING" --with-colons --list-keys | grep ^[ps]ub | cut -d: -f5`
-    master_keys=`aptkey_execute "$GPG_SH" --keyring "$MASTER" --with-colons --list-keys | grep ^pub | cut -d: -f5`
+    all_add_keys="$(aptkey_execute "$GPG_SH" --keyring "$ADD_KEYRING" --with-colons --list-keys | grep ^[ps]ub | cut -d: -f5)"
+    master_keys="$(aptkey_execute "$GPG_SH" --keyring "$MASTER" --with-colons --list-keys | grep ^pub | cut -d: -f5)"
 
     # ensure there are no colisions LP: #857472
     for all_add_key in $all_add_keys; do
@@ -99,6 +116,9 @@ add_keys_with_verify_against_master_keyring() {
 # the archive-keyring keys needs to be signed with the master key
 # (otherwise it does not make sense from a security POV)
 net_update() {
+    local APT_DIR='/'
+    eval $(apt-config shell APT_DIR Dir)
+
     # Disabled for now as code is insecure (LP: #1013639 (and 857472, 1013128))
     APT_KEY_NET_UPDATE_ENABLED=""
     eval $(apt-config shell APT_KEY_NET_UPDATE_ENABLED APT::Key::Net-Update-Enabled)
@@ -112,7 +132,7 @@ net_update() {
     fi
     # in theory we would need to depend on wget for this, but this feature
     # isn't useable in debian anyway as we have no keyring uri nor a master key
-    if ! which wget >/dev/null 2>&1; then
+    if ! command_available 'wget'; then
        echo >&2 "ERROR: an installed wget is required for a network-based update"
        exit 1
     fi
@@ -122,7 +142,7 @@ net_update() {
     keyring="${APT_DIR}/var/lib/apt/keyrings/$(basename "$ARCHIVE_KEYRING_URI")"
     old_mtime=0
     if [ -e $keyring ]; then
-       old_mtime=$(stat -c %Y $keyring)
+       old_mtime=$(stat -c %Y "$keyring")
     fi
     (cd  "${APT_DIR}/var/lib/apt/keyrings"; wget --timeout=90 -q -N "$ARCHIVE_KEYRING_URI")
     if [ ! -e "$keyring" ]; then
@@ -136,6 +156,13 @@ net_update() {
 }
 
 update() {
+    if [ -z "$APT_KEY_DONT_WARN_ON_DANGEROUS_USAGE" ]; then
+       echo >&2 "Warning: 'apt-key update' is deprecated and should not be used anymore!"
+       if [ -z "$ARCHIVE_KEYRING" ]; then
+           echo >&2 "Note: In your distribution this command is a no-op and can therefore be removed safely."
+           exit 0
+       fi
+    fi
     if [ ! -f "$ARCHIVE_KEYRING" ]; then
        echo >&2 "ERROR: Can't find the archive-keyring"
        echo >&2 "Is the &keyring-package; package installed?"
@@ -172,6 +199,10 @@ remove_key_from_keyring() {
     for KEY in "$@"; do
        local FINGERPRINTS="${GPGHOMEDIR}/keyringfile.keylst"
        get_fingerprints_of_keyring "$KEYRINGFILE" > "$FINGERPRINTS"
+
+       # strip leading 0x, if present:
+       KEY="$(echo "${KEY#0x}" | tr -d ' ')"
+
        # check if the key is in this keyring
        if ! grep -iq "^[0-9A-F]*${KEY}$" "$FINGERPRINTS"; then
            continue
@@ -213,14 +244,11 @@ foreach_keyring_do() {
            $ACTION "$TRUSTEDFILE" "$@"
        fi
        local TRUSTEDPARTS="/etc/apt/trusted.gpg.d"
-       eval $(apt-config shell TRUSTEDPARTS Dir::Etc::TrustedParts/d)
+       eval "$(apt-config shell TRUSTEDPARTS Dir::Etc::TrustedParts/d)"
        if [ -d "$TRUSTEDPARTS" ]; then
-           # strip / suffix as gpg will double-slash in that case (#665411)
-           local STRIPPED_TRUSTEDPARTS="${TRUSTEDPARTS%/}"
-           if [ "${STRIPPED_TRUSTEDPARTS}/" = "$TRUSTEDPARTS" ]; then
-               TRUSTEDPARTS="$STRIPPED_TRUSTEDPARTS"
-           fi
-           for trusted in $(run-parts --list "$TRUSTEDPARTS" --regex '^.*\.gpg$'); do
+           TRUSTEDPARTS="$(readlink -f "$TRUSTEDPARTS")"
+           local TRUSTEDPARTSLIST="$(cd /; find "$TRUSTEDPARTS" -mindepth 1 -maxdepth 1 -name '*.gpg')"
+           for trusted in $(echo "$TRUSTEDPARTSLIST" | sort); do
                if [ -s "$trusted" ]; then
                    $ACTION "$trusted" "$@"
                fi
@@ -278,7 +306,7 @@ 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"
+    local PUBRING="$(readlink -f "${GPGHOMEDIR}/pubring.gpg")"
     # if a --keyring was given, just use this one
     if [ -n "$FORCED_KEYRING" ]; then
        if [ -s "$FORCED_KEYRING" ]; then
@@ -289,13 +317,12 @@ merge_all_trusted_keyrings_into_pubring() {
        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 "$TRUSTEDFILE" || true
-              for parts in $(find -L "$TRUSTEDPARTS" -type f -name '*.gpg'); do
-                 cat "$parts" || true
-              done
-           } > "$PUBRING" 2>/dev/null
+           rm -f "$PUBRING"
+           if [ -s "$TRUSTEDFILE" ]; then
+               cat "$TRUSTEDFILE" > "$PUBRING"
+           fi
+           TRUSTEDPARTS="$(readlink -f "$TRUSTEDPARTS")"
+           (cd /; find "$TRUSTEDPARTS" -mindepth 1 -maxdepth 1 -name '*.gpg' -exec cat {} + >> "$PUBRING";)
        elif [ -s "$TRUSTEDFILE" ]; then
            cp --dereference "$TRUSTEDFILE" "$PUBRING"
        fi
@@ -345,7 +372,7 @@ setup_merged_keyring() {
        FORCED_KEYRING="${GPGHOMEDIR}/forcedkeyid.gpg"
        TRUSTEDFILE="${FORCED_KEYRING}"
        echo "#!/bin/sh
-exec sh \"${GPG}\" --keyring \"${TRUSTEDFILE}\" \"\$@\"" > "${GPGHOMEDIR}/gpg.1.sh"
+exec sh '($(escape_shell "${GPG}")' --keyring '$(escape_shell "${TRUSTEDFILE}")' \"\$@\"" > "${GPGHOMEDIR}/gpg.1.sh"
        GPG="${GPGHOMEDIR}/gpg.1.sh"
        # 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
@@ -357,12 +384,12 @@ exec sh \"${GPG}\" --keyring \"${TRUSTEDFILE}\" \"\$@\"" > "${GPGHOMEDIR}/gpg.1.
           touch "${GPGHOMEDIR}/pubring.gpg" "${GPGHOMEDIR}/pubring.orig.gpg"
        fi
        echo "#!/bin/sh
-exec sh \"${GPG}\" --keyring \"${GPGHOMEDIR}/pubring.gpg\" \"\$@\"" > "${GPGHOMEDIR}/gpg.1.sh"
+exec sh '$(escape_shell "${GPG}")' --keyring '$(escape_shell "${GPGHOMEDIR}/pubring.gpg")' \"\$@\"" > "${GPGHOMEDIR}/gpg.1.sh"
        GPG="${GPGHOMEDIR}/gpg.1.sh"
     else
        create_new_keyring "$TRUSTEDFILE"
        echo "#!/bin/sh
-exec sh \"${GPG}\" --keyring \"${TRUSTEDFILE}\" \"\$@\"" > "${GPGHOMEDIR}/gpg.1.sh"
+exec sh '$(escape_shell "${GPG}")' --keyring '$(escape_shell "${TRUSTEDFILE}")' \"\$@\"" > "${GPGHOMEDIR}/gpg.1.sh"
        GPG="${GPGHOMEDIR}/gpg.1.sh"
     fi
 }
@@ -453,6 +480,14 @@ if [ -z "$command" ]; then
 fi
 shift
 
+cleanup_gpg_home() {
+    if [ -z "$GPGHOMEDIR" ]; then return; fi
+    if command_available 'gpgconf'; then
+       GNUPGHOME="${GPGHOMEDIR}" gpgconf --kill gpg-agent >/dev/null 2>&1 || true
+    fi
+    rm -rf "$GPGHOMEDIR"
+}
+
 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
@@ -464,61 +499,92 @@ create_gpg_home() {
        fi
     fi
     GPGHOMEDIR="$(mktemp -d)"
-    CURRENTTRAP="${CURRENTTRAP} rm -rf '${GPGHOMEDIR}';"
+    CURRENTTRAP="${CURRENTTRAP} cleanup_gpg_home;"
     trap "${CURRENTTRAP}" 0 HUP INT QUIT ILL ABRT FPE SEGV PIPE TERM
+    if [ -z "$GPGHOMEDIR" ]; then
+       echo "ERROR: Could not create temporary gpg home directory in apt-key ($TMPDIR)"
+       exit 28
+    fi
     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
+    # crude detection if we are called from a maintainerscript where the
+    # package depends on gnupg or not. We accept recommends here as
+    # well as the script hopefully uses apt-key optionally then like e.g.
+    # debian-archive-keyring for (upgrade) cleanup did
+    if [ -n "$DPKG_MAINTSCRIPT_PACKAGE" ] && [ -z "$APT_KEY_DONT_WARN_ON_DANGEROUS_USAGE" ]; then
+       if ! dpkg-query --show --showformat '${Pre-Depends}${Depends}${Recommends}\n' "$DPKG_MAINTSCRIPT_PACKAGE" 2>/dev/null | grep -q gnupg; then
+           cat >&2 <<EOF
+Warning: The $DPKG_MAINTSCRIPT_NAME maintainerscript of the package $DPKG_MAINTSCRIPT_PACKAGE
+Warning: seems to use apt-key (provided by apt) without depending on gnupg or gnupg2.
+Warning: This will BREAK in the future and should be fixed by the package maintainer(s).
+Note: Check first if apt-key functionality is needed at all - it probably isn't!
+EOF
+       fi
+    fi
+    eval "$(apt-config shell GPG_EXE Apt::Key::gpgcommand)"
+    if [ -n "$GPG_EXE" ] && command_available "$GPG_EXE"; then
        true
-    elif which gpg >/dev/null 2>&1; then
+    elif command_available 'gpg'; then
        GPG_EXE="gpg"
-    elif which gpg2 >/dev/null 2>&1; then
+    elif command_available 'gpg2'; then
        GPG_EXE="gpg2"
+    elif command_available 'gpg1'; then
+       GPG_EXE="gpg1"
     else
-       echo >&2 "Error: gnupg or gnupg2 do not seem to be installed,"
-       echo >&2 "Error: but apt-key requires gnupg or gnupg2 for this operation."
+       echo >&2 "Error: gnupg, gnupg2 and gnupg1 do not seem to be installed,"
+       echo >&2 "Error: but apt-key requires gnupg, gnupg2 or gnupg1 for this operation."
        echo >&2
        exit 255
     fi
 
     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"
-    touch "$SECRETKEYRING"
-
     # 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
+    touch "${GPGHOMEDIR}/empty.gpg"
     if ! "$GPG_EXE" --ignore-time-conflict --no-options --no-default-keyring \
-       --homedir "$GPGHOMEDIR" --quiet --check-trustdb --keyring "$SECRETKEYRING" >"${GPGHOMEDIR}/gpgoutput.log" 2>&1; then
+       --homedir "$GPGHOMEDIR" --quiet --check-trustdb --keyring "${GPGHOMEDIR}/empty.gpg" >"${GPGHOMEDIR}/gpgoutput.log" 2>&1; then
        cat >&2 "${GPGHOMEDIR}/gpgoutput.log"
        false
     fi
-    # tell gpg that it shouldn't try to maintain a trustdb file
+
+    # now tell gpg that it shouldn't try to maintain this trustdb file
     echo "#!/bin/sh
-exec \"${GPG_EXE}\" --ignore-time-conflict --no-options --no-default-keyring \\
-   --homedir \"${GPGHOMEDIR}\" --no-auto-check-trustdb --trust-model always \"\$@\"" > "${GPGHOMEDIR}/gpg.0.sh"
+exec '$(escape_shell "${GPG_EXE}")' --ignore-time-conflict --no-options --no-default-keyring \\
+--homedir '$(escape_shell "${GPGHOMEDIR}")' --no-auto-check-trustdb --trust-model always \"\$@\"" > "${GPGHOMEDIR}/gpg.0.sh"
     GPG_SH="${GPGHOMEDIR}/gpg.0.sh"
     GPG="$GPG_SH"
 
+    # We don't usually need a secret keyring, of course, but
     # 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"
+       if ! aptkey_execute "$GPG" -v --batch --import "$FORCED_SECRET_KEYRING" >"${GPGHOMEDIR}/gpgoutput.log" 2>&1; then
+           cat >&2 "${GPGHOMEDIR}/gpgoutput.log"
+           false
+       fi
+    else
+       # and then, there are older versions of gpg which panic and implode
+       # if there isn't one available - and writeable for imports
+       # and even if not output is littered with the creation of a secring,
+       # so lets call import once to have it create what it wants in silence
+       echo -n | aptkey_execute "$GPG" --batch --import >/dev/null 2>&1 || true
     fi
+}
 
-    # older gpg versions need a secring file, but newer versions take it as
-    # a hint to start a migration from earlier versions. The file is empty
-    # 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 | aptkey_execute "$GPG" --batch --import >/dev/null 2>&1 || true
+warn_on_script_usage() {
+    if [ -n "$APT_KEY_DONT_WARN_ON_DANGEROUS_USAGE" ]; then
+       return
+    fi
+    # (Maintainer) scripts should not be using apt-key
+    if [ -n "$DPKG_MAINTSCRIPT_PACKAGE" ]; then
+       echo >&2 "Warning: apt-key should not be used in scripts (called from $DPKG_MAINTSCRIPT_NAME maintainerscript of the package ${DPKG_MAINTSCRIPT_PACKAGE})"
+    elif [ ! -t 1 ]; then
+       echo >&2 "Warning: apt-key output should not be parsed (stdout is not a terminal)"
+    fi
 }
 
 if [ "$command" != 'help' ] && [ "$command" != 'verify' ]; then
@@ -527,6 +593,7 @@ fi
 
 case "$command" in
     add)
+       warn_on_script_usage
        requires_root
        setup_merged_keyring
        aptkey_execute "$GPG" --quiet --batch --import "$@"
@@ -534,11 +601,13 @@ case "$command" in
        aptkey_echo "OK"
         ;;
     del|rm|remove)
+       # no script warning here as removing 'add' usage needs 'del' for cleanup
        requires_root
        foreach_keyring_do 'remove_key_from_keyring' "$@"
        aptkey_echo "OK"
         ;;
     update)
+       warn_on_script_usage
        requires_root
        setup_merged_keyring
        update
@@ -550,17 +619,17 @@ case "$command" in
        net_update
        merge_back_changes
        ;;
-    list)
-       foreach_keyring_do 'run_cmd_on_keyring' --list-keys "$@"
-       ;;
-    finger*)
+    list|finger*)
+       warn_on_script_usage
        foreach_keyring_do 'run_cmd_on_keyring' --fingerprint "$@"
        ;;
     export|exportall)
+       warn_on_script_usage
        merge_all_trusted_keyrings_into_pubring
        aptkey_execute "$GPG_SH" --keyring "${GPGHOMEDIR}/pubring.gpg" --armor --export "$@"
        ;;
     adv*)
+       warn_on_script_usage
        setup_merged_keyring
        aptkey_echo "Executing: $GPG $*"
        aptkey_execute "$GPG" "$@"
@@ -569,11 +638,12 @@ case "$command" in
     verify)
        GPGV=''
        eval $(apt-config shell GPGV Apt::Key::gpgvcommand)
-       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';
+       if [ -n "$GPGV" ] && command_available "$GPGV"; then true;
+       elif command_available 'gpgv'; then GPGV='gpgv';
+       elif command_available 'gpgv2'; then GPGV='gpgv2';
+       elif command_available 'gpgv1'; then GPGV='gpgv1';
        else
-          echo >&2 'ERROR: gpgv or gpgv2 required for verification'
+          echo >&2 'ERROR: gpgv, gpgv2 or gpgv1 required for verification'
           exit 29
        fi
        # for a forced keyid we need gpg --export, so full wrapping required