gpgtool/gpgtool

346 lines
10 KiB
Bash
Executable file

#!/usr/bin/env zsh
_dependencies=("echo" "gpg" "ln" "ls" "mktemp" "mkdir" "mv" "rm" "udisksctl")
_tempdir=/tmp/gpgtool.tempdir
stty sane
# Sets up GPGtool. Creates temporary GNUPG home directory, mounts key device if
# given, checks for key directory.
init () {
local dev
local mount
local opts
local keypath
# Check if already initialized:
if [[ -f "${_tempdir}" ]]
then
local dir="$(cat ${_tempdir})"
echo "GPGtool already initialized at ${dir}."
if [[ -f "${dir}/env" ]]
then
source "${dir}/env"
[[ ! -z "${dev}" ]] && echo -e "\tdevice: ${dev}"
[[ ! -z "${mountdev}" ]] && echo -e "\tmapped device: ${mountdev}"
[[ ! -z "${keypath}" ]] && echo -e "\tkey path: ${keypath}"
fi
exit 1
fi
zparseopts -A opts -D -E -- d: p: -device: -path:
for opt in "${(@k)opts}"
do
case $opt in
-d|--device)
dev=$opts[$opt];
;;
-p|--path)
keypath=$opts[$opt];
;;
esac
done
# If no device given, check for ENV:
[[ -z "${dev}" ]] && dev=${GPGTOOLKEYDEV}
# If no path given, check for ENV:
[[ -z "${keypath}" ]] && keypath=${GPGTOOLKEYPATH}
# Unlock device:
if [[ ! -z "${dev}" ]] && udisksctl info -b ${dev} | grep -q "org.freedesktop.UDisks2.Encrypted"
then
echo "Unlocking device ${dev}"
local mountdev=$(udisksctl unlock -b "${dev}")
[[ $? != 0 ]] && echo "Error unlocking device ${dev}, aborting …" && exit 1
mountdev="$(echo $mountdev | awk '{ print $4 }')"
mountdev=${mountdev:0:-1}
fi
# Mount device:
[[ -z ${mountdev} ]] && mountdev=${dev}
if [[ ! -z ${mountdev} ]]
then
echo "Mounting device ${mountdev}"
mountpath=$(udisksctl mount -b "${mountdev}")
if [[ $? != 0 ]]
then
echo "Error mounting device ${mount}, aborting …"
[[ "${mountdev}" != "${dev}" ]] && udisksctl lock -b ${dev}
exit 1
fi
mountpath="$(echo $mountpath | awk '{ print $4 }')"
fi
# Finalize path:
[[ ! -z "${mountpath}" ]] && keypath=${mountpath}/${keypath}
# Check if keypath actually exists:
[[ ! -d "${keypath}" ]] && echo "Key path ${keypath} is not a directory, aborting …" && exit 64
# Create temporary working directory:
local tempdir=$(mktemp -d --tmpdir)
mkdir ${tempdir}/gpghome
chmod 700 ${tempdir}/gpghome
# Save state:
cat > ${tempdir}/env << EOF
dev="${dev}"
mountdev="${mountdev}"
keypath="${keypath}"
EOF
echo -n "${tempdir}" > /tmp/gpgtool.tempdir
echo "GPGtool initialized. Temporary working directory: ${tempdir}, key path ${keypath}."
echo ""
echo "Try \`gpgtool list\` next."
}
# Cleans up GPGtool. Clears the temp directory and closes the mounted volume.
clear () {
_checkinit
# Unmount device:
if [[ ! -z "${mountdev}" ]]
then
echo "Unmounting ${mountdev}"
udisksctl unmount -b "${mountdev}"
[[ $? != 0 ]] && echo "Error unmounting ${mountdev}, aborting …" && exit 1
fi
# Lock device:
if [[ "${dev}" != "${mountdev}" ]]
then
echo "Locking ${dev}"
udisksctl lock -b "${dev}"
fi
# Remove temporary working directory:
echo "Removing temporary working directory ${tempdir}"
rm -rf "${tempdir}"
rm -f "${_tempdir}"
}
# Lists all keys stored in the key path.
list () {
_checkinit
# Abort if no keys found:
[[ -z "$(ls ${keypath})" ]] &&
echo "No keys found in key path. Try \`gpgtool create\`" && exit 0
echo -e "Key Fingerprint\t\t\t\t\tDescription"
perl -E 'say "-" x 82'
for key in $(ls ${keypath})
do
echo -en "${key}\t"
[[ -f "${keypath}/${key}/info" ]] && echo "$(cat ${keypath}/${key}/info)" || echo ""
done
echo
pkdir="${tempdir}/gpghome/private-keys-v1.d"
if [[ -z "$(ls -l ${pkdir} | grep ^l)" ]]
then
echo "No open keys. Try \`gpgtool open FINGERPRINT [FINGERPRINT …]\`."
else
echo "Currently Opened Keys:"
ls -l ${pkdir} | grep ^l | awk {'print $NF'}
fi
}
# Opens a key from the key path. Imports into temporary GPG and links the secret
# key.
open () {
_checkinit
for id in $@
do
key=$(ls ${keypath}/${id}/*.key)
[[ $? != 0 ]] && \
echo "${id} does not seem to be a valid key folder, aborting …" && exit 1
if [[ -f "${tempdir}/gpghome/private-keys-v1.d/$(basename ${key})" ]]
then
echo "Key ${id} already opened."
else
gpg --homedir "${tempdir}/gpghome" --import-options keep-ownertrust \
--import "${keypath}/${id}/${id}.private.asc"
[[ $? != 0 ]] && exit 1
ln -sf ${key} ${tempdir}/gpghome/private-keys-v1.d/
echo -e "\nKey ${id} opened. Try \`gpgtool extend ${id}\` next."
fi
done
}
# Closes a key from the key path. Deletes the secret key. does not delete the
# actual key; that will be disposed of in the end anyway.
close () {
_checkinit
for id in $@
do
key=$(ls ${keypath}/${id}/*.key)
[[ $? != 0 ]] && \
echo "${id} does not seem to be a valid key folder, aborting …" && exit 1
rm -f "${tempdir}/gpghome/private-keys-v1.d/$(basename ${key})"
done
}
# Closes all keys from the key path.
closeall () {
_checkinit
for key in $(ls ${keypath}/*/*.key)
do
rm -f "${tempdir}/gpghome/private-keys-v1.d/$(basename ${key})"
done
}
# Creates a new key and stores it in the key path.
create () {
_checkinit
echo "----- Create master key:"
gpg --homedir "${tempdir}/gpghome" --full-generate-key
echo -n "----- Enter the fingerprint of your new key: "
read id
echo "----- Add dedicated signing key; end with \`save\`:"
gpg --homedir "${tempdir}/gpghome" --edit-key ${id} addkey
echo "----- Moving secret key …"
keygrip=$(gpg --homedir "${tempdir}/gpghome" --with-keygrip --list-key ${id} | grep Keygrip | head -n 1 | awk {'print $3'})
mkdir -p "${keypath}/${id}/"
mv "${tempdir}/gpghome/private-keys-v1.d/${keygrip}.key" "${keypath}/${id}/"
echo "----- Exporting …"
exdir="${keypath}/${id}/$(date +%F)"
mkdir -p "${exdir}"
gpg --homedir "${tempdir}/gpghome" --armor --export-secret-keys ${id} > "${keypath}/${id}/${id}.private.asc"
gpg --homedir "${tempdir}/gpghome" --armor --export ${id} > "${keypath}/${id}/${id}.public.asc"
cp ${keypath}/${id}/${id}.*.asc ${exdir}
mv "${tempdir}/gpghome/openpgp-revocs.d/${id}.rev" "${keypath}/${id}/"
echo "----- Restoring secret key for further modification …"
ln -sf ${keypath}/${id}/${keygrip}.key ${tempdir}/gpghome/private-keys-v1.d/
echo "----- Remember to import / send & close when done!"
# FIXXE: add /info file!
}
# (Re-)exports an open key after editing.
save () {
_checkinit
id=${1}
echo "----- Removing secret key …"
keygrip=$(gpg --homedir "${tempdir}/gpghome" --with-keygrip --list-key ${id} | grep Keygrip | head -n 1 | awk {'print $3'})
rm ${tempdir}/gpghome/private-keys-v1.d/${keygrip}.key
echo "----- Exporting …"
exdir="${keypath}/${id}/$(date +%F)"
mkdir -p $exdir
gpg --homedir "${tempdir}/gpghome" --armor --export-secret-keys ${id} > ${keypath}/${id}/${id}.private.asc
gpg --homedir "${tempdir}/gpghome" --armor --export ${id} > ${keypath}/${id}/${id}.public.asc
cp ${keypath}/${id}/${id}.*.asc ${exdir}
echo "----- Restoring secret key …"
ln -sf ${keypath}/${id}/${keygrip}.key ${tempdir}/gpghome/private-keys-v1.d/
echo -e "\nYou probably want to \`gpgtool import ${id}\` to load the updated key into your regular keyring."
}
# Extends an open key.
extend () {
_checkinit
id=${1}
echo "----- Step 1: change expiry date; end with \`save\`:"
gpg --homedir "${tempdir}/gpghome" --edit-key ${id} expire
echo "----- Step 2: add new encryption key; end with \`save\`:"
gpg --homedir "${tempdir}/gpghome" --edit-key ${id} addkey
echo "----- Step 3: add new sign key; end with \`save\`:"
gpg --homedir "${tempdir}/gpghome" --edit-key ${id} addkey
echo "----- Removing secret key …"
keygrip=$(gpg --homedir "${tempdir}/gpghome" --with-keygrip --list-key ${id} | grep Keygrip | head -n 1 | awk {'print $3'})
rm ${tempdir}/gpghome/private-keys-v1.d/${keygrip}.key
echo "----- Exporting …"
exdir="${keypath}/${id}/$(date +%F)"
mkdir -p $exdir
gpg --homedir "${tempdir}/gpghome" --armor --export-secret-keys ${id} > ${keypath}/${id}/${id}.private.asc
gpg --homedir "${tempdir}/gpghome" --armor --export ${id} > ${keypath}/${id}/${id}.public.asc
cp ${keypath}/${id}/${id}.*.asc ${exdir}
echo "----- Restoring secret key …"
ln -sf ${keypath}/${id}/${keygrip}.key ${tempdir}/gpghome/private-keys-v1.d/
echo -e "\nYou probably want to \`gpgtool import ${id}\` to load the updated key into your regular keyring."
}
# Sends keys to key servers.
send () {
_checkinit
# FIXXME: bugged?
# zparseopts -D -E -- -keyserver=servers:
[[ -z "${servers}" ]] && servers=( $GPGTOOLKEYSERVERS )
[[ -z "${servers}" ]] && echo "No key servers given, aborting …" && exit 1
for id in $@
do
for url in ${servers}
do
gpg --homedir "${tempdir}/gpghome" --send-keys --keyserver ${url} ${id}
done
done
}
# Imports keys into your regular GnuPG keyring.
import () {
_checkinit
id=$1
local keyfile=${keypath}/${id}/${id}.private.asc
[[ ! -f "${keyfile}" ]] && echo "Cannot find secret key ${keyfile}, aborting …" && exit 1
echo "Importing key into your regular GnuPG keyring …"
gpg --import-options keep-ownertrust --import "${keyfile}"
}
# Prints usage information.
usage () {
echo FIXXME
}
# Abort if not currently initialized, otherwise load state.
_checkinit () {
[[ ! -f "${_tempdir}" ]] && echo "GPGtool not currently initialized, aborting …" && exit 1
tempdir="$(cat /tmp/gpgtool.tempdir)"
[[ ! -f "${tempdir}/env" ]] && \
echo "State information missing. Try \`gpgtool clear && gpgtool init\`. Aborting …" && exit 1
source "${tempdir}/env"
}
# Abort for missing dependencies:
for dep in ${_dependencies}
do
if ! command -v ${dep} >/dev/null 2>&1
then
[[ -z "${deps}" ]] && deps=${dep} || deps="${deps}, ${dep}"
fi
done
[[ ! -z "${deps}" ]] && echo "Missing dependencies: ${deps}." && exit 1
# Abort for no arguments:
[[ $# == 0 ]] && usage && exit 64
# Handle -h / --help / help and exit:
[[ "$1" =~ "(-h|--help|help)" ]] && usage && exit 0
# Source config file:
for dir in "$(dirname $0)" "${HOME}/.config/gpgtool" "${XDG_CONFIG_HOME}/gpgtool"
do
[[ -f "${dir}/gpgtool.conf" ]] && source "${dir}/gpgtool.conf"
done
# Handle Subcommands:
[[ "$(type -w $1)" =~ "(.+\s+)?function" && ! "$1" =~ "_.*" ]] \
&& $@ \
|| echo "Invalid command: $1. See \`$0 --help\`." >&2 && exit 64