#!/usr/bin/env bash # GPGSecure is a shell script that manages GPG encrypted archives # Copyright (C) 2018 Aaron Ball # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . export KEY=${KEY:-} # GPG key to encrypt the container with export DIR # Directory path to present the gpg archive to export TMP # Temp directory in memory to decrypt to trap shutdown SIGINT SIGTERM SIGKILL SIGQUIT SIGHUP shutdown() { gpgtar -e --recipient "${KEY}" -o "${DIR}.tar.gpg" . cd - 2>/dev/null 1>/dev/null # Shred all files in memory find "${TMP}" -type f -exec shred -n 100 -f -u "{}" \; # Delete the link rm "${DIR}" # Delete the temp dir from memory rm -rf "${TMP}" sync exit } writeback() { trap shutdown SIGINT SIGTERM SIGKILL SIGQUIT SIGHUP local tmp # Temp file in memory to write re-encrypted container to. This is # used for faster writebacks to storage for bigger containers. # This also protects data, bigger containers take longer to write # to storage, leaving a bigger window for data corruption. local sleep=20 local perms # Permissions of the dest encrypted file. Used for setting perms # on temp archive to avoid overwriting permissions on move # Read dest archive permissions, if it exists, else set to 700 if [ -f "${DIR}.tar.gpg" ]; then perms=$(stat -c %a "${DIR}.tar.gpg") else perms=700 fi cd "${TMP}" # Sync back to disk every ${sleep} seconds while [ 0 ]; do # Create temp archive for writing back so we don't risk corrupting the # actual destination archive in case of crash. Protect with 700 perms. tmp="$(mktemp /tmp/XXXXXXXXXXXX)" chmod 700 "${tmp}" # Write encrypted archive to temp file gpgtar -e --recipient "${KEY}" -o "${tmp}" . if [ $? -gt 0 ]; then printf 'WARNING: Something went wrong syncing back to encrypted storage\n' printf 'Your data is likely in danger.\n' printf 'If you see this message more than once, take a manual backup\n' fi # Update perms of temp file to match destination archive so we don't # overwrite those of the destination archive on move # TODO: This is a split second of permissions danger. We should find a way # to remediate this. Set perms on dest archive *after* move instead? chmod "${perms}" "${tmp}" mv "${tmp}" "${DIR}.tar.gpg" if [ $? -gt 0 ]; then printf 'WARNING: Something went wrong syncing back to encrypted storage\n' printf 'Your data is likely in danger.\n' printf 'If you see this message more than once, take a manual backup\n' fi sleep ${sleep} & wait $! done } open() { local archive="${1}" # Convert DIR to absolute path to avoid cd issues local dirname="$(cd $(dirname ${archive}) && pwd)" local basename="$(basename ${archive})" local dir="${dirname}/${basename}" # Create a temp dir in memory to extract to for safety export TMP=$(mktemp -d /tmp/dec-XXXXXXXXXXXXXX) # Link! ln -s "${TMP}" "${dir}" if [ ! -f "${dir}.tar.gpg" ]; then # Tell the user if that encrypted archive does not exist. printf 'Encrypted archive does not exist. Creating.\n' else # Extract the encrypted tarchive if it exists gpgtar --decrypt --directory "${TMP}" "${dir}.tar.gpg" fi writeback & echo $! > "${dirname}/.${basename}.pid" } status() { local archive=${1} local dirname="$(dirname ${archive})" local basename="$(basename ${archive})" local pidfile="${dirname}/.${basename}.pid" # If no pidfile, assume closed if [ ! -f "${pidfile}" ]; then printf '%s is closed\n' "${archive}" return 0 fi local pid="$(cat ${pidfile})" ps "${pid}" 2>/dev/null 1>/dev/null if [ $? -eq 0 ]; then printf '%s is open\n' "${archive}" elif [ $? -gt 0 ]; then printf '%s is closed but a stale pidfile was found. Removing\n' "${archive}" rm -f "${pidfile}" else printf '%s is closed\n' "${archive}" fi } close() { local archive=${1} local dirname="$(dirname ${archive})" local basename="$(basename ${archive})" local pidfile="${dirname}/.${basename}.pid" local pid="$(cat ${pidfile})" ps "${pid}" 2>/dev/null 1>/dev/null if [ $? -gt 0 ]; then printf "Stale pidfile detected but share is not open. Removing\n" rm -f "${pidfile}" return 1 else # Send SIGTERM (15) to tell the process to exit cleanly kill -15 "${pid}" [ $? -eq 0 ] && rm "${pidfile}" && return 0 printf 'Error closing archive "%s"\n' "${archive}" return 1 fi } pathtoabs() { local path="${1}" if [ -f "${path}" ]; then cd $(dirname ${path}) printf "%s/%s\n" "$(pwd)" "$(basename ${path})" return 0 elif [ -d "${path}" ]; then cd ${path} && pwd return 0 elif [ ! -e "${path}" ]; then printf -- "%s/%s\n" "$(pwd)" "${path}" return 0 fi return 1 } main() { local action="${1}" local archive="${2}" # Input validation if [ -z "${action:-}" ]; then printf 'Action (open, close, or status) required\n' return 1 fi if [ -z "${archive:-}" ]; then printf 'Archive to decrypt required\n' return 1 fi if [ -z "${KEY}" ]; then printf 'KEY variable unset. Cannot re-encrypt. Exiting.\n' return 1 fi gpg --list-keys ${KEY} 2>/dev/null 1>/dev/null if [ $? -gt 0 ]; then printf 'Unknown key "%s". Cannot proceed.\n' "${KEY}" return 1 fi local dirname="$(dirname ${archive})" local basename="$(basename ${archive})" export DIR="$(pathtoabs ${dirname}/${basename})" if [ "${action}" = 'open' ]; then # Check if already open if [ -f "${dirname}/.${basename}.pid" ]; then printf 'ERROR: Archive "%s" is already open\n' "${archive}" return 1 else printf 'Opening!\n' open "${archive}" return $? fi elif [ "${action}" = 'close' ]; then # Check if already closed if [ ! -f "${dirname}/.${basename}.pid" ]; then printf 'ERROR: Archive "%s" is not open\n' "${archive}" exit 1 else printf 'Closing!\n' close "${archive}" return $? fi elif [ "${action}" = 'status' ]; then status "${archive}" return $? fi # If we make it here, something went wrong. printf 'ERROR: Unknown action "%s"\n' "${action}" return 1 } main ${@}