#!/bin/bash # # Common functions. # # Setup main configuration and load preferences function keyringer_config_load { if [ -f "$HOME/.$NAME" ]; then echo "Converting legacy configuration scheme..." mv "$HOME/.$NAME" "$HOME/.$NAME.tmp" mkdir "$HOME/.$NAME" mv "$HOME/.$NAME.tmp" "$CONFIG" fi if [ ! -e "$CONFIG" ]; then echo "Creating $CONFIG..." mkdir -p `dirname $CONFIG` touch "$CONFIG" chmod 600 "$CONFIG" echo "# Keyringer config file." > "$CONFIG" echo "" >> "$CONFIG" fi keyringer_config_load_preferences } # Load config preferences function keyringer_config_load_preferences { # Load custom keyring preferences if [ ! -z "$PREFERENCES" ] && [ -f "$PREFERENCES" ]; then source "$PREFERENCES" elif [ ! -z "$PREFERENCES" ]; then touch $PREFERENCES fi } # Load a parameter from config function keyringer_config { if [ -z "$CONFIG" ]; then echo "Your have to set CONFIG variable in the code" exit 1 elif [ -e "$CONFIG" ]; then grep -e "^$1=" "$CONFIG" | tail -n 1 | cut -d = -f 2 | sed -e 's/"//g' -e "s/'//g" | sed -e 's/ *#.*$//' else echo "Config file not found: $CONFIG" exit 1 fi } # Return the list of recipients function keyringer_recipients { grep -v '^#' "$1" | grep -v '^$' | awk '{ print "-r " $2 }' | xargs } # Check if keyringer has a given action function keyringer_has_action { if [ -z "$ACTIONS" ]; then echo "Your have to set ACTIONS variable in the code" exit 1 fi if [ -e "$ACTIONS/$1" ] && [ ! -d "$ACTIONS/$1" ]; then true else false fi } # Execute an action function keyringer_exec { # Setup action="$1" basedir="$2" shift 2 # Dispatch if keyringer_has_action "$action"; then "$ACTIONS/$action" "$basedir" $* err="$?" if [ "$err" != "0" ]; then exit "$err" fi fi } # Return a filename with correct extension function keyringer_filename { if [ -z "$1" ]; then return else printf "%s/%s.asc\n" "$(dirname "$1")" "$(basename "$1" .asc)" fi } # Check if a folder is inside a git repository function keyringer_is_git { if [ -z "$1" ]; then false elif [ ! -d "$1" ]; then false elif [ -d "$1/.git" ]; then true else ( cd "$1" && git status &> /dev/null ) if [ "$?" != "128" ]; then true else false fi fi } # Check the security of a temporary folder function keyringer_check_tmp { local path="$1" local mount # Mode check if [ -z "$path" ] || [ ! -d "$path" ] || [ ! -w "$path" ] || [ ! -x "$path" ]; then return 1 fi # Ramdisk check mount="`df "$path" | sed -n '$p' | awk '{ print $NF }'`" mount -l -t tmpfs | awk '{ print $3 }' | grep -q -e "^$mount$" } # Setup a temporary file function keyringer_set_tmpfile { local tmp local candidate local candidates="$TMPDIR $TMP /tmp /run/shm" if [ -z "$BASEDIR" ]; then echo "Please set BASEDIR before creating a tmp file" exit 1 fi # Ramdisk check for candidate in $candidates; do if keyringer_check_tmp $candidate; then tmp="$candidate/keyringer.`whoami`" break fi done # Set base temp folder if [ -z "$tmp" ]; then echo "WARNING: neither one of $candidates is mounted in a tmpfs/ramdisk, using $BASEDIR/tmp as fallback." echo "Make sure that $BASEDIR is atop of an encrypted volume." echo "Press any key to continue, Ctrl-C to abort" read key tmp="$BASEDIR/tmp" # Just to be sure keyringer_git_ignore 'tmp/*' fi # Determine template if [ -z "$1" ]; then template="$tmp/keyringer.XXXXXXXXXX" else template="$tmp/XXXXXXXXXX.$1" fi mkdir -p "$tmp" if [ "$2" == "-d" ]; then TMPWORK="$(mktemp -d "$template")" else TMPWORK="$(mktemp "$template")" fi if [ "$?" != "0" ]; then printf "Error: can't set TMPWORK %s\n" "$TMPWORK" exit 1 fi trap "keyringer_unset_tmpfile $TMPWORK; exit" INT TERM EXIT } # Shred files function keyringer_shred { local path="$1" local tool local message="Removing" if [ -z "$path" ]; then return elif [ ! -e "$path" ]; then return fi # Get shred implementation if which wipe &> /dev/null; then tool="wipe" elif which shred &> /dev/null; then tool="shred" else # Worst implementation message="WARNING $message" tool="rm" fi echo "$message $path using $tool..." if [ -d "$path" ]; then find $path -exec $tool -f {} \; rmdir $path else $tool -f "$path" fi } # Remove a temporary file function keyringer_unset_tmpfile { if [ -z "$1" ]; then echo "No tmp file set" fi keyringer_shred "$1" if [ "$?" != "0" ]; then echo "Warning: could not delete file $1. Please delete it manually as it might have sensitive information." exit 1 fi } # Add a pattern into gitignore function keyringer_git_ignore { if [ ! -z "$BASEDIR/.gitignore" ]; then echo "$1" > "$BASEDIR/.gitignore" keyringer_exec git "$BASEDIR" add .gitignore else if ! grep -q -e "^$1$" "$BASEDIR/.gitignore"; then echo "$1" >> "$BASEDIR/.gitignore" fi fi } # Set needed environment variables and do basic checks. function keyringer_set_env { if [ -z "$1" ]; then echo "Error: missing arguments for keyringer_set_env" exit 1 fi ACTIONS="`dirname $0`" BASENAME="`basename $0`" BASEDIR="$1" SUBCOMMAND="$2" KEYDIR="$BASEDIR/keys" RECIPIENTS_BASE="config/recipients" RECIPIENTS="$BASEDIR/$RECIPIENTS_BASE" OPTIONS="$BASEDIR/config/options" VERSION_INFO="$BASEDIR/config/version" if [ -z "$BASEDIR" ]; then keyringer_action_usage exit 1 fi if [ ! -e "$RECIPIENTS" ]; then echo "No recipient config was found" exit 1 fi if [ -z "$EDITOR" ]; then if type sensible-editor > /dev/null 2>&1 ; then EDITOR=sensible-editor elif type editor > /dev/null 2>&1 ; then EDITOR=editor else echo "You have to set EDITOR env variable" exit 1 fi fi if [ ! -f "$OPTIONS" ]; then echo "No option config was found" exit 1 fi if [ ! -z "$KEYID" ]; then GPG="gpg -u $KEYID" else GPG="gpg" fi # Check keyring config version keyringer_check_version # Upgrade configuration keyringer_upgrade # Check recipients file keyringer_check_recipients $SUBCOMMAND # Ensure that keydir exists mkdir -p "$KEYDIR" && chmod 700 "$KEYDIR" } # Configuration version tracking to help keyring upgrades function keyringer_check_version { if [ "$KEYRINGER_CHECK_VERSION" == "false" ]; then if [ -f "$VERSION_INFO" ]; then VERSION="`cat $VERSION_INFO`" else VERSION="" fi return fi if [ ! -f "$VERSION_INFO" ]; then echo "Configuration version file not found, trying to pull from remotes..." # Do not use keyringer_exec as it would trigger keyringer_check_version again ( cd "$BASEDIR" && git pull ) if [ ! -f "$VERSION_INFO" ]; then echo "Creating configuration version file..." echo 0 > "$VERSION_INFO" if keyringer_is_git "$BASEDIR"; then keyringer_exec git "$BASEDIR" add config/version echo "Pushing configuration version file to remotes..." for remote in "$BASEDIR/.git/refs/remotes/*"; do keyringer_exec git "$BASEDIR" push $remote master done fi fi fi VERSION="`cat $VERSION_INFO`" # Check if config version is supported by keyringer if [ "$VERSION" != "$CONFIG_VERSION" ]; then echo "Configuration version differs from keyringer version, trying to pull from remotes" # Do not use keyringer_exec as it would trigger keyringer_check_version again ( cd "$BASEDIR" && git pull ) if [ "$VERSION" != "$CONFIG_VERSION" ]; then NEWEST="`echo -e "$VERSION\n$CONFIG_VERSION" | sort -V | tail -n 1`" if [ "$NEWEST" == "$VERSION" ]; then echo "Fatal: config version: $CONFIG_VERSION / config version: $VERSION" echo "Please upgrade your keyringer application" exit 1 fi fi fi } # Configuration upgrades function keyringer_upgrade { # Variable used to hold applied version upgrades local version="$VERSION" if [ "$KEYRINGER_CHECK_VERSION" == "false" ]; then return fi # Upgrade 0.1 if [ "$version" == "0" ]; then if [ ! -d "$RECIPIENTS" ]; then echo "Converting recipients to the new scheme..." mv $RECIPIENTS $RECIPIENTS.tmp mkdir $RECIPIENTS mv $RECIPIENTS.tmp $RECIPIENTS/default keyringer_exec git "$BASEDIR" add $RECIPIENTS_BASE/default keyringer_exec git "$BASEDIR" commit -m "Config-upgrade-0.1" fi # Done 0.1 upgrade echo "0.1" > $VERSION_INFO version="0.1" fi # Upgrade 0.X #if [ "$version" == "0.1" ]; then # echo "Upgrading to 0.X config format..." # # # Done 0.X upgrade # echo "0.X" > $VERSION_INFO # version="0.X" #fi # Update version information if [ "$CONFIG_VERSION" != "$VERSION" ]; then echo $CONFIG_VERSION > $VERSION_INFO keyringer_exec git "$BASEDIR" add config/version keyringer_exec git "$BASEDIR" commit -m "Config-update-$CONFIG_VERSION" echo "Upgrade to version $CONFIG_VERSION completed, pushing to remotes..." for remote in "$BASEDIR/.git/refs/remotes/*"; do keyringer_exec git "$BASEDIR" push $remote master done fi } # Get a file argument function keyringer_get_file { FILE="$(keyringer_filename "$RELATIVE_PATH/$1")" if [ -z "$FILE" ]; then keyringer_action_usage exit 1 elif [ ! -f "$KEYDIR/$FILE" ]; then echo "File not found: $KEYDIR/$FILE" exit 1 fi } # Get a new file argument function keyringer_get_new_file { # File must not contain spaces if [ ! -z "$2" ] ; then FILE="`echo "$*" | sed -e 's/ /_/g'`" else FILE="$1" fi # Sanitize and complete file name FILE="`echo $FILE | sed -e 's/[^A-Za-z0-9.\/\-]/_/g'`" # Warn user about file name change if [ "`basename "$*"`" != "`basename $FILE`" ]; then echo "Sanitizing destination filename to `basename $FILE`" fi # Complete file name FILE="$RELATIVE_PATH/$(keyringer_filename "$FILE")" if [ -z "$*" ]; then keyringer_action_usage exit 1 fi } # Get a command argument function keyringer_get_command { # Aditional parameters COMMAND="$1" if [ -z "$COMMAND" ]; then keyringer_action_usage command exit 1 fi } # Run the action usage function keyringer_action_usage { if [ "`type -t "keyringer_usage_$BASENAME"`" == "function" ]; then # Use custom action usage "keyringer_usage_$BASENAME" else # Default usage if [ "$1" == "command" ]; then echo "Usage: keyringer $BASENAME [arguments]" else echo "Usage: keyringer $BASENAME " fi fi } # Return available actions function keyringer_show_actions { ls $ACTIONS } # Usage function keyringer_usage { local keyrings="$(ls --color=never `dirname $CONFIG` | sed -e 's/config//' | xargs)" printf "Keyringer $KEYRINGER_VERSION\n" printf "Usage: %s [arguments]\n\n" "$BASENAME" printf "Available commands: \n\n" keyringer_show_actions | sed -e 's/^/\t/' # Display only when not in a keyring context if [ ! -z "$keyrings" ] && [ -z "$1" ]; then printf "\tinit [remote]\n\n" $BASENAME printf "Available keyrings: %s \n" "$keyrings" fi } # Check recipients function keyringer_check_recipients { if [ "$KEYRINGER_CHECK_RECIPIENTS" == "false" ]; then return fi # Check if recipients file is empty. if [ "`grep -vE "^#|^$" "$RECIPIENTS"/* | wc -l`" == 0 ] && [ "$SUBCOMMAND" != "edit" ]; then echo "Fatal: no recipients configured for this keyring." echo "Please edit your recipients file first." exit 1 fi # Check recipients header for updates. if grep -qe ' XXXXXXXX$' "$RECIPIENTS"/*; then echo "Updating recipients file..." sed -i -e 's/ XXXXXXXX$/ XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX/' "$RECIPIENTS"/* fi if [ "$1" == "edit" ]; then # Don't do the other checks at edit mode. return fi for recipient in $(cat "$RECIPIENTS"/* | grep -v '^#' | awk '{ print $2 }'); do size=$(echo "$recipient" | wc -c) if (( $size < 41 )); then echo "Fatal: please set the full GPG signature hash for key ID $recipient:" cat <<-EOF Please provide a full OpenPGP fingerprint, for example: john@doe.com ABCD1234ABCD12345678ABCD1234ABCD12345678 Short key ids (for example, DEADBEEF or DECAF123) are not allowed in recipient files because they are easy to spoof. Researchers have proven that it is possible to build fake keys to match any possible short key id by using a few gigabytes of disk space, and a day of computation on common hardware. Otherwise, the encryption can be broken, if someone spoofs a short key id, and causes a participant in a keyringer repository to encrypt secrets to a fake key. EOF exit 1 else gpg --list-key "$recipient" &> /dev/null if [ "$?" != "0" ]; then echo "Fatal: no such key $recipient on your GPG keyring." echo "Please check for this key or fix the recipient file." exit 1 fi fi done } # Set recipients function keyringer_set_recipients { if [ -z "$1" ]; then keyringer_set_default_recipients else candidate="$1" candidate_no_extension="`echo $1 | sed -e 's/.asc$//'`" # Find the first matching recipient while [ ! -z "$candidate" ] && [ "$candidate" != "." ] && [ "$candidate" != "/" ]; do if [ -e "$RECIPIENTS/$candidate" ]; then RECIPIENTS_FILE="$RECIPIENTS/$candidate" RECIPIENTS_FILE_BASE="$RECIPIENTS_BASE/$candidate" return elif [ -e "$RECIPIENTS/$candidate_no_extension" ]; then RECIPIENTS_FILE="$RECIPIENTS/$candidate_no_extension" RECIPIENTS_FILE_BASE="$RECIPIENTS_BASE/$candidate_no_extension" return fi candidate="`dirname $candidate`" done keyringer_set_default_recipients "$1" fi } # Set default recipients function keyringer_set_default_recipients { if [ -e "$RECIPIENTS/default" ]; then RECIPIENTS_FILE="$RECIPIENTS/default" RECIPIENTS_FILE_BASE="$RECIPIENTS_BASE/default" else echo "Fatal: no suitable recipient file found for path $1" exit 1 fi } # Set a new recipient, avoid file checks function keyringer_set_new_recipients { if [ -z "$1" ]; then keyringer_set_default_recipients else RECIPIENTS_FILE="$RECIPIENTS/$1" RECIPIENTS_FILE_BASE="$RECIPIENTS_BASE/$1" fi } # Create a new recipients file function keyringer_create_new_recipients { if [ ! -e "$1" ]; then mkdir -p "`dirname $1`" echo "# Use entries in the form of 'john@doe.com XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX'" > "$1" echo "" >> "$1" fi } # Setup environment if [ "$(basename "$0")" != "keyringer" ]; then keyringer_config_load_preferences keyringer_set_env $* fi