#!/usr/bin/env bash
#
# Script to remove GPG user (recipient) with git-crypt
#
# It will re-initialize git-crypt for the repository and re-add all keys except
# the one requested for removal.
#
# Note: You still need to change all your secrets to fully protect yourself.
# Removing a user will prevent them from reading future changes but they will
# still have a copy of the data up to the point of their removal.
#
# Usage: see 'git-crypt-rm-gpg-user.sh -h'
#    git-crypt-rm-gpg-user.sh -l   : list GPG keys configured within git-crypt for the current directory
#    git-crypt-rm-gpg-user.sh -r <FULL-GPG-FINGERPRINT> :  remove specified key from git-crypt configuration
#        Ex: remove-gpg-user.sh -r 3BC18383F838C0B815B961480F8CAF5467D
#
# Typical workflow (assuming the script is placed in ~/bin/) :
#
#     cd /path/to/protected/repo
#     git checkout -b  git-crypt-remove
#     git-crypt-rm-gpg-user.sh -l           # List configured GPG keys
#     git-crypt-rm-gpg-user.sh -r <KEYID>
#     git push origin git-crypt-remove      # publish the branch
#
# Merge (sync with your collaborators for the appropriate timing):
#     git checkout master # or devel or whatever main branch
#     git-crypt lock
#     git merge git-crypt-remove
#     # release the repository
#
# Pulling for your collaborators
#     cd /path/to/protected/repo
#     git-crypt lock
#     git pull origin
#
# You can check the full, 64-bit (8-byte) key ID for a key within your keyring with
#     gpg -k [email | pattern]
#     gpg -k --with-colons  [email | pattern | id] |  awk -F: '/^pub:/ { print $5 }'
# See https://git.gnupg.org/cgi-bin/gitweb.cgi?p=gnupg.git;a=blob_plain;f=doc/DETAILS
#
# The script will create multiple commits to your repo. Feel free to squash them
# all down to one.
#
# Based on https://github.com/AGWA/git-crypt/issues/47#issuecomment-212734882
#
#
# GIST source https://gist.github.com/glogiotatidis/e0ab45ed5575a9d7973390dace0552b0
# amended to work on Mac OS and with cosmetic changes for a more convenient interface in
#   https://gist.github.com/Falkor/7b29f16f5f79404fe41476be0d992783
#   Script is now shellcheck compliant and safer: https://gist.github.com/thomsh/ed14fa82cf43a6b283c1eea9574fb76e
#################
set -eo pipefail

KEY_TO_REMOVE=
KEY_FINGERPRINT=
# CMD_PREFIX=

# HELPERS
print_error_and_exit () {
  echo "*** ERROR *** $*"
  echo "Usage: see '$(basename $0) -h'"
  exit 1
}

really_continue() {
    echo "/!\\ WARNING: $*"
    echo "Are you sure you want to continue? [Y|n]"
    read -r ans
    case $ans in
	      n*|N*) exit 1;;
    esac
}

print_usage () {
    cat <<EOF
$(basename $0): Remove a given GPG key from a git-crypt enabled repository.

Options:
   -l : list GPG keys configured within git-crypt
   [-n] -r <FULL-GPG-FINGERPRINT> : remove key from git-crypt configuration

Typical workflow:

    cd /path/to/protected/repo
    git checkout -b  git-crypt-remove
    $(basename $0) -l           # List configured GPG keys
    $(basename $0) -r <KEYID>

You can check the full, 64-bit (8-byte) key ID for a key within your keyring with
     gpg -k [email | pattern]
     gpg -k --with-colons  [email | pattern | id] |  awk -F: '/^pub:/ { print \$5 }'

See https://git.gnupg.org/cgi-bin/gitweb.cgi?p=gnupg.git;a=blob_plain;f=doc/DETAILS
EOF
}

###
# Print info for a given key from your keyring
##
print_key_info() {
    [ -z "${1:-}" ] && print_error_and_exit "${FUNCNAME[0]} missing text argument"
    local key
    local gpgid
    local fpr
    key="$1"
    gpgid="$(gpg --list-keys --with-colons "${key}" |  awk -F: '/^pub:/ { print $5 }')"
    fpr="$(gpg -k --with-colons "${key}" | awk -F: '/^fpr:/ { print $10; exit; }')"  # Only first fpr
    echo "==== Key Fingerprint: ${fpr} ==="
    echo "==== Long GPG ID: ${gpgid}"
    gpg --list-key "$key" || print_error_and_exit "Couldn't find info about $key"
    export KEY_FINGERPRINT="$fpr"
}

###
# List GPG keys configured for protecting this repository
##
list_git_crypt_keys() {
    for f in .git-crypt/keys/default/0/*.gpg; do
        # key="$(basename "${f}" .gpg)"
        print_key_info "$(basename "${f}" .gpg)"
        # gpgid=$(gpg --list-keys --with-colons $key |  awk -F: '/^pub:/ { print $5 }')
        # echo "==== Key Fingerprint: ${key} ==="
        # echo "==== Long GPG ID: ${gpgid}"
        # gpg --list-key "$key" || print_error_and_exit "Couldn't find info about $key"
    done
}

##### Let's go #####
# Check for options
while [ $# -ge 1 ]; do
    case $1 in
        -h | --help)    print_usage;  exit 0;;
        -l | --list)    list_git_crypt_keys;    exit 0;;
        -r | --remove)  shift; KEY_TO_REMOVE=$1;;
        # -n | --dry-run) CMD_PREFIX=echo ;;
    esac
    shift
done

if [ -z "${KEY_TO_REMOVE:-}" ];then
  print_error_and_exit "No key to remove has been indicated"
fi

print_key_info "${KEY_TO_REMOVE}"

if [ -z "${KEY_FINGERPRINT:-}" ]; then
  print_error_and_exit "Unable to retrieve key fingerprint"
fi

if [ -z "$(command -v rsync)" ]; then
  print_error_and_exit "This script use rsync, sadly rsync is not found on your system"
fi

really_continue "About to remove the GPG Key ID ${KEY_TO_REMOVE} from Git-crypt configuration"

set -x # enable debug just in case we need to dig
set -euo pipefail # enforce mode with nounset (-u)

# Below code adapted from @glogiotatidis - https://gist.github.com/glogiotatidis/e0ab45ed5575a9d7973390dace0552b0
TMPDIR="$(mktemp -d)"
CURRENT_DIR="$(git rev-parse --show-toplevel)"
BASENAME="$(basename "$(pwd)")"
TMPFILE=list-encrypted-files.txt

cat <<EOF
TMPDIR=${TMPDIR}
CURRENT_DIR=${CURRENT_DIR}
EOF
# Unlock the directory, we need to copy encrypted versions of the files
git crypt unlock

# Work on copy.
cp -a -- "$(pwd)" "$TMPDIR"

pushd "$TMPDIR/$BASENAME"
git stash # stash potential typechange

# Remove encrypted files and git-crypt
git crypt status -e |sed -E 's/^\s+encrypted: //g' > "${TMPFILE}"
if [ -s "${TMPFILE}" ]; then
    <"${TMPFILE}" xargs rm
    #xargs -I {} -- rm -- {} < "${TMPFILE}"
    git commit -a -m "Remove encrypted files"
fi
rm -rf -- .git-crypt
git commit -m "Remove git-crypt (in particular configuration related to key ${KEY_TO_REMOVE})" .git-crypt
rm -rf .git/git-crypt

# Re-initialize git crypt
git crypt init

# Add existing users, except the key to remove
find  "${CURRENT_DIR}/.git-crypt/keys/default/0" -type f -iname '*gpg' -print|while read -r keyfilename
do
  basename="$(basename "$keyfilename")"
  key="${basename%.*}"
  if [[ "$key" == "$KEY_FINGERPRINT" ]]; then
    echo "ignoring key ${KEY_FINGERPRINT} to be removed"
    continue
  fi
  # check if the key is expired - second field is 'e'
  # See https://git.gnupg.org/cgi-bin/gitweb.cgi?p=gnupg.git;a=blob_plain;f=doc/DETAILS
  expired="$(gpg --with-colons --list-keys  --with-fingerprint "$key" | awk -F: '/^pub:/ { print $2; }')"
  if [[ "${expired}" == "e" ]]; then
    echo "/!\\ WARNING: key $key expired thus not integrated in the git-crypt keyring"
    echo "/!\\ WARNING: key details: "
    print_key_info "$key"
    continue
  fi
  git crypt add-gpg-user "$key"
done

cd "${CURRENT_DIR}"
while read encrypted_file; do
  rsync -rp -R "${encrypted_file}" "${TMPDIR}/${BASENAME}"
done < ${TMPDIR}/${BASENAME}/${TMPFILE}

cd "${TMPDIR}/${BASENAME}"
while read encrypted_file; do
    git add "${encrypted_file}"
done < ${TMPDIR}/${BASENAME}/${TMPFILE}

git commit -m "New encrypted files" || true
popd

git crypt lock
git pull "${TMPDIR}/${BASENAME}"

rm -rf -- "${TMPDIR}"
