#!/bin/sh

set -e
unset GREP_OPTIONS

# We don't use a secret keyring, of course, but gpg panics and
# implodes if there isn't one available

GPG_CMD="gpg --ignore-time-conflict --no-options --no-default-keyring --secret-keyring /etc/apt/secring.gpg --trustdb-name /etc/apt/trustdb.gpg"
GPG="$GPG_CMD --keyring /etc/apt/trusted.gpg"


MASTER_KEYRING=""
ARCHIVE_KEYRING_URI=""
#MASTER_KEYRING=/usr/share/keyrings/debian-master-keyring.gpg
#ARCHIVE_KEYRING_URI=http://ftp.debian.org/debian/debian-archive-keyring.gpg

ARCHIVE_KEYRING=/usr/share/keyrings/debian-archive-keyring.gpg
REMOVED_KEYS=/usr/share/keyrings/debian-archive-removed-keys.gpg

cmd_add() {
    local FILE="$1"
    if [ "$INTERACTIVE" = "0" ]; then
        add_keys_to_keyring "$FILE"
    else
        prepare_tmp
        $GPG_TMP --batch --import "$FILE" || return 1
        confirm_tmpkeys && add_tmpkeys_to_keyring
        destroy_tmp
    fi
}

add_keys_to_keyring() {
    local FILE="$1"
    $GPG --quiet --batch --import "$FILE" && echo "OK"
}

add_tmpkeys_to_keyring() {
    $GPG_TMP -q --no-tty --export | add_keys_to_keyring -
}

add_keys_with_verify_against_master_keyring() {
    ADD_KEYRING=$1
    MASTER=$2
    
    if [ ! -f "$ADD_KEYRING" ]; then
	echo "ERROR: '$ADD_KEYRING' not found"
	return
    fi 
    if [ ! -f "$MASTER" ]; then
	echo "ERROR: '$MASTER' not found"
	return
    fi

    # when adding new keys, make sure that the archive-master-keyring
    # 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`
    master_keys=`$GPG_CMD --keyring $MASTER --with-colons --list-keys | grep ^pub | cut -d: -f5`
    for add_key in $add_keys; do
	ADDED=0
	for master_key in $master_keys; do
	    if $GPG_CMD --keyring $ADD_KEYRING --list-sigs --with-colons $add_key | grep ^sig | cut -d: -f5 | grep -q $master_key; then
		$GPG_CMD --quiet --batch --keyring $ADD_KEYRING --export $add_key | $GPG --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
    done
}

# update the current archive signing keyring from a network URI
# the archive-keyring keys needs to be signed with the master key
# (otherwise it does not make sense from a security POV)
net_update() {
    if [ -z "$ARCHIVE_KEYRING_URI" ]; then
	echo "ERROR: no location for the archive-keyring given" 
    fi
    if [ ! -d /var/lib/apt/keyrings ]; then
	mkdir -p /var/lib/apt/keyrings
    fi
    keyring=/var/lib/apt/keyrings/$(basename $ARCHIVE_KEYRING)
    old_mtime=0
    if [ -e $keyring ]; then
	old_mtime=$(stat -c %Y $keyring)
    fi
    (cd  /var/lib/apt/keyrings; wget -q -N $ARCHIVE_KEYRING_URI)
    if [ ! -e $keyring ]; then
	return
    fi
    new_mtime=$(stat -c %Y $keyring)
    if [ $new_mtime -ne $old_mtime ]; then
	echo "Checking for new archive signing keys now"
	add_keys_with_verify_against_master_keyring $keyring $MASTER_KEYRING
    fi
}

update() {
    if [ ! -f $ARCHIVE_KEYRING ]; then
	echo >&2 "ERROR: Can't find the archive-keyring"
	echo >&2 "Is the debian-archive-keyring package installed?"
	exit 1
    fi

    # add new keys from the package;

    # we do not use add_keys_with_verify_against_master_keyring here,
    # because "update" is run on regular package updates.  A
    # attacker might as well replace the master-archive-keyring file
    # in the package and add his own keys. so this check wouldn't
    # add any security. we *need* this check on net-update though
    $GPG_CMD --quiet --batch --keyring $ARCHIVE_KEYRING --export | $GPG --import

    # remove no-longer supported/used keys
    keys=`$GPG_CMD --keyring $REMOVED_KEYS --with-colons --list-keys | grep ^pub | cut -d: -f5`
    for key in $keys; do
	if $GPG --list-keys --with-colons | grep ^pub | cut -d: -f5 | grep -q $key; then
	    $GPG --quiet --batch --delete-key --yes ${key}
	fi
    done
}

# variables for handling temporary key data
TMP=""
TMP_GPGHOME=""
GPG_TMP=""

prepare_tmp() {
    if [ -n "$TMP" ]; then
        echo "prepare_temp() called twice, aborting"
        exit 1
    fi
    TMP=$(mktemp -d -t apt-key.XXXXXX)
    TMP_GPGHOME="$TMP/gpg"
    
    trap destroy_tmp EXIT INT

    mkdir -p "$TMP_GPGHOME"
    chmod go-rwx "$TMP_GPGHOME"
    # create empty keyrings
    > $TMP_GPGHOME/pubring.gpg
    > $TMP_GPGHOME/secring.gpg

    GPG_TMP="gpg --homedir $TMP_GPGHOME --no-options --no-default-keyring --batch"
}

destroy_tmp() {
     if [ -z "$TMP" ]; then
        echo "destroy_tmp() called without prepare_tmp() before, aborting"
        exit 1
    fi
    # uninstall handler
    trap - EXIT INT
   
    rm -fr $TMP
    TMP=""
    TMP_GPGHOME=""
    GPG_TMP=""
}

fetch() {
    local KEYID="$1"
    local KEYSERVER="wwwkeys.de.pgp.net"

    if [ "$INTERACTIVE" = "0" ]; then
        echo "For security reasons, fetching keys from a keyserver does only work in --interactive mode."
        return 1
    fi

    if [ -z "$KEYID" ]; then
        echo "Nothing to fetch"
        return 1
    fi
    
    $GPG_TMP -q --no-tty --keyserver $KEYSERVER --recv-keys "$KEYID" || return 1

    if confirm_tmpkeys; then
        # keys confirmed, import them in the keyring
        add_tmpkeys_to_keyring
    else
        echo "Aborted"
        return 1
    fi
}

confirm_tmpkeys() {
    echo
    echo "Please compare the key identities and fingerprints below to an"
    echo "independent source to confirm their integrity."
    echo
    
    $GPG_TMP --fingerprint

    CONFIRMATION="Yes, I will"

    echo "Do your trust this key and wish to add it to your apt keyring?"
    echo "So answer with '$CONFIRMATION'."
    read -p " > " ANSWER
    
    [ "$ANSWER" = "$CONFIRMATION" ]
}


usage() {
    echo "Usage: apt-key [--interactive|-i] [command] [arguments]"
    echo
    echo "Manage apt's list of trusted keys"
    echo
    echo "  apt-key add <file>          - add the key contained in <file> ('-' for stdin)"
    echo "  apt-key del <keyid>         - remove the key <keyid>"
    echo "  apt-key export <keyid>      - output the key <keyid>"
    echo "  apt-key exportall           - output all trusted keys"
    echo "  apt-key update              - update keys using the keyring package"
    echo "  apt-key net-update          - update keys using the network"
    echo "  apt-key list                - list keys"
    echo "  apt-key finger              - list fingerprints"
    echo "  apt-key -i fetch <keyid>    - fetch key from keyserver (required interactive mode)"
    echo "  apt-key adv                 - pass advanced options to gpg"
    echo
}

INTERACTIVE=0

if [ "$1" = "--interactive" ] || [ "$1" = "-i" ]; then
    INTERACTIVE=1
    shift
fi

command="$1"
if [ -z "$command" ]; then
    usage
    exit 1
fi
shift

if [ "$command" != "help" ] && ! 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."
    echo >&2
fi

case "$command" in
    add)
        cmd_add "$1"
        ;;
    del|rm|remove)
        $GPG --quiet --batch --delete-key --yes "$1"
        echo "OK"
        ;;
    update)
	update
	;;
    net-update)
	net_update
	;;
    list)
        $GPG --batch --list-keys
        ;;
    finger*)
        $GPG --batch --fingerprint
        ;;
    export)
        $GPG --armor --export "$1"
        ;;
    exportall)
        $GPG --armor --export
        ;;
    adv*)
        echo "Executing: $GPG $*"
        $GPG $*
        ;;
    fetch)
        prepare_tmp;
        fetch $*
        destroy_tmp;
        ;;
    help)
        usage
        ;;
    *)
        usage
        exit 1
        ;;
esac
