346 lines
10 KiB
Bash
Executable file
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
|