diff --git a/dotfiles/commonfunc b/dotfiles/commonfunc index fc7b938..88a202b 100644 --- a/dotfiles/commonfunc +++ b/dotfiles/commonfunc @@ -8,6 +8,156 @@ # {{@@ header() @@}} # +# age encryption / decryption helpers +# based on https://git.sr.ht/~digital/secretFiles +if [[ $(command -v age) ]]; then + # get recipients for age file to encrypt with + ageGetRecipientsList() { + local target="${1}" + local search="${target}" + local recipients=( "-R" "secrets/hostkeys/masterkey.pubkey" ) + local recip + while true; do + if test -d "${search}.recipients"; then + for recip in $(ls ${search}.recipients) ; do + if test -n "${recip}"; then + recipients+=("-R" "${search}.recipients/${recip}") + fi + done + elif test -f "${search}.recipients"; then + recipients+=( "-R" "${search}.recipients") + fi + if test "$(realpath ${search})" = "$(realpath $(pwd))"; then + break + fi + search=$(dirname "${search}") + done + echo "${recipients[@]}" + } + + age-gen-key() { + set -efu -o pipefail + + local keyname="${1}" + + mkdir -p "secrets/hostkeys/" + echo "generating new keys for host ${keyname}"; + age-keygen \ + 2> "secrets/hostkeys/${keyname}.pubkey" \ + | age -p --armor -e -o "secrets/hostkeys/${keyname}.privkey" + sed -i 's/Public key: //' "secrets/hostkeys/${keyname}.pubkey" + + set +efu +o pipefail + } + + age-import-secret() { + set -euf -o pipefail + + local secret_path="${1}" + local recipients_list=$(ageGetRecipientsList "${secret_path}") + local dirname="$(dirname ${secret_path})" + local identity="${MASTERKEY_FILE:-secrets/hostkeys/masterkey.privkey}" + + mkdir -p "${dirname}" + + age ${recipients_list[@]} --encrypt --armor --output "${secret_path}" + + set +efu +o pipefail + } + + age-edit-file() { + set -euf -o pipefail + local current_umask=$(umask) + umask 177 + + local secret_path="${1}" + local tmp_path="$(mktemp -p /dev/shm)" + local recipients_list=$(ageGetRecipientsList "${secret_path}") + local identity="${MASTERKEY_FILE:-$([[ -f "$(realpath "secrets/hostkeys/masterkey.privkey")" ]] && echo -n "$(realpath "secrets/hostkeys/masterkey.privkey")" || echo -n "/dev/stdin")}" + # [[ -f "$(realpath "secrets/hostkeys/masterkey.privkey")" ]] && local identity="$(realpath "secrets/hostkeys/masterkey.privkey")" || + + if test -e "${secret_path}"; then + set +e +o pipefail + + age \ + --decrypt \ + --identity "${identity}" \ + --output "${tmp_path}" \ + "${secret_path}" || local decrypt_failed=true + + set -e -o pipefail + else + # if file descriptor 0 is not a terminal, ie if /dev/stdin is a pipe + if [ ! -t 0 ]; then + cat "${identity}" > /dev/null + fi + fi + + if [[ ! ${decrypt_failed:-} ]]; then + local mod_time_before=$(stat --format "%Y" "${tmp_path}") + ${EDITOR} "${tmp_path}" + local mod_time_after=$(stat --format "%Y" "${tmp_path}") + + if test "${mod_time_before}" != "${mod_time_after}"; then + echo "change detected, reencrypting file" > /dev/stderr + age ${recipients_list[@]} --encrypt --armor --output "${secret_path}" "${tmp_path}" + else + echo "no change detected, not reencrypting file" > /dev/stderr + fi + fi + + rm "${tmp_path}" + + umask ${current_umask} + set +efu +o pipefail + } + + age-reencrypt-all() { + set -euf -o pipefail + local current_umask=$(umask) + umask 177 + + local identity="${1:-/dev/stdin}" + local identity_file="$(mktemp -u -p /dev/shm)" + + # make the identity file reuseable, in case it actually is /dev/stdin + umask 177 + cat "${identity}" > "${identity_file}" + + find "secrets" -type f -not -name "*\.recipients" \ + | grep -v "^secrets/hostkeys/"| while read line; do + if ! grep -q "^-----BEGIN AGE ENCRYPTED FILE-----$" "${line}"; then + echo "skipping unecrypted file '${line}'" + continue + fi + local recipients=$(ageGetRecipientsList "${line}") + echo "reencrypting '${line}' for recipients ${recipients[@]}" + local content="$(age --decrypt \ + --identity "${identity_file}" \ + "${line}" \ + )" || { + echo "ERROR: failed decryption of '${line}'" > /dev/stderr + echo "aborting and leaving secrets store in an inconsistent state" > /dev/stderr + exit 2 + } + if test $? -eq 0 ; then + echo -n "${content}" \ + | age ${recipients[@]} \ + --encrypt \ + --armor \ + --output "${line}" + fi + done + + rm "${identity_file}" + + umask ${current_umask} + set +efu +o pipefail + + echo "SUCCESS" > /dev/stderr + } +fi + # eza - set aliasses for eza to use it as ls replacement if [[ $(command -v eza) ]]; then ezafunc() {