diff options
Diffstat (limited to 'lib')
-rwxr-xr-x | lib/keyringer/actions/check | 29 | ||||
-rwxr-xr-x | lib/keyringer/actions/find | 2 | ||||
-rwxr-xr-x | lib/keyringer/actions/git | 9 | ||||
-rwxr-xr-x | lib/keyringer/actions/mv | 2 | ||||
-rwxr-xr-x | lib/keyringer/actions/options | 2 | ||||
-rwxr-xr-x | lib/keyringer/actions/preferences | 2 | ||||
-rwxr-xr-x | lib/keyringer/actions/xclip | 54 | ||||
-rw-r--r-- | lib/keyringer/completions/bash/keyringer | 2 | ||||
-rw-r--r-- | lib/keyringer/completions/zsh/_keyringer | 2 | ||||
-rwxr-xr-x | lib/keyringer/functions | 251 |
10 files changed, 265 insertions, 90 deletions
diff --git a/lib/keyringer/actions/check b/lib/keyringer/actions/check new file mode 100755 index 0000000..c80fa8f --- /dev/null +++ b/lib/keyringer/actions/check @@ -0,0 +1,29 @@ +#!/bin/bash +# +# Check a keyring. +# +# See also some useful OpenPGP maintenance scripts: +# +# - git://lair.fifthhorseman.net/~mjgoins/cur +# - https://gitorious.org/key-report +# - https://github.com/ilf/gpg-maintenance.git +# - https://github.com/EtiennePerot/parcimonie.sh +# - https://gaffer.ptitcanardnoir.org/intrigeri/code/parcimonie/ +# +# This script can run from a crontab, client or server side to check +# keyringer health status. + +# Load functions +LIB="`dirname $0`/../functions" +source "$LIB" || exit 1 + +# The following should run automatically from keyringer_check_recipients +# and keyringer_check_repository: +# +# Pull the keyring repository. +# Git maintenance operations. +# Fetch absent keys from all recipients. +# Check key expirations + +# This should be done here: +# TODO: Check canaries' timestamps, warning by mail if configured by user preferences. diff --git a/lib/keyringer/actions/find b/lib/keyringer/actions/find index 21afc7a..9b18d66 100755 --- a/lib/keyringer/actions/find +++ b/lib/keyringer/actions/find @@ -15,5 +15,5 @@ shift ARGS="`echo "$*" | sed -e "s|^/*||"`" # Run find command -cd "$KEYDIR/$RELATIVE_PATH" && find -iname "*$ARGS*" | sed -e 's|^./||g' +cd "$KEYDIR/$RELATIVE_PATH" && find | grep -i "$ARGS" | sed -e 's|^./||g' cd "$CWD" diff --git a/lib/keyringer/actions/git b/lib/keyringer/actions/git index 108ccea..d4e7aa4 100755 --- a/lib/keyringer/actions/git +++ b/lib/keyringer/actions/git @@ -13,12 +13,5 @@ CWD="`pwd`" # Run git command shift -# Set working folder -if [ ! -z "$RELATIVE_PATH" ]; then - WORK="$KEYDIR/$RELATIVE_PATH" -else - WORK="$BASEDIR" -fi - -mkdir -p "$WORK" && cd "$WORK" && git $* +mkdir -p "$BASEDIR" && cd "$BASEDIR" && git $* cd "$CWD" diff --git a/lib/keyringer/actions/mv b/lib/keyringer/actions/mv index aaf6772..daac7b0 100755 --- a/lib/keyringer/actions/mv +++ b/lib/keyringer/actions/mv @@ -25,4 +25,4 @@ if ! echo "$ORIG" | grep -q '*' && [ ! -e "$KEYDIR/$RELATIVE_PATH/$ORIG" ]; then fi # Run move command -keyringer_exec git "$BASEDIR" mv $ORIG $FILE +keyringer_exec git "$BASEDIR" mv "keys/$RELATIVE_PATH/$ORIG" "keys/$FILE" diff --git a/lib/keyringer/actions/options b/lib/keyringer/actions/options index 3bf0e43..b210e1a 100755 --- a/lib/keyringer/actions/options +++ b/lib/keyringer/actions/options @@ -1,6 +1,6 @@ #!/bin/bash # -# Recipient management. +# Repository options management. # # Load functions diff --git a/lib/keyringer/actions/preferences b/lib/keyringer/actions/preferences index f7507a7..114f9ac 100755 --- a/lib/keyringer/actions/preferences +++ b/lib/keyringer/actions/preferences @@ -1,6 +1,6 @@ #!/bin/bash # -# Manipulate preferences. +# Manipulate user preferences. # # Load functions diff --git a/lib/keyringer/actions/xclip b/lib/keyringer/actions/xclip index b28984f..7afdf05 100755 --- a/lib/keyringer/actions/xclip +++ b/lib/keyringer/actions/xclip @@ -7,31 +7,33 @@ # Function thanks to Password Store by Jason A. Donenfeld <Jason@zx2c4.com> # distributed under GPLv2+: http://www.zx2c4.com/projects/password-store/ clip() { - # This base64 business is a disgusting hack to deal with newline inconsistancies - # in shell. There must be a better way to deal with this, but because I'm a dolt, - # we're going with this for now. - - before="$(xclip -o -selection clipboard | base64)" - echo -n "$1" | xclip -selection clipboard - ( - sleep 45 - now="$(xclip -o -selection clipboard | base64)" - if [[ $now != $(echo -n "$1" | base64) ]]; then - before="$now" - fi - - # It might be nice to programatically check to see if klipper exists, - # as well as checking for other common clipboard managers. But for now, - # this works fine -- if qdbus isn't there or if klipper isn't running, - # this essentially becomes a no-op. - # - # Clipboard managers frequently write their history out in plaintext, - # so we axe it here: - qdbus org.kde.klipper /klipper org.kde.klipper.klipper.clearClipboardHistory &>/dev/null - - echo "$before" | base64 -d | xclip -selection clipboard - ) & disown - echo "Copied $2 to clipboard. Will clear in 45 seconds." + # This base64 business is a disgusting hack to deal with newline inconsistancies + # in shell. There must be a better way to deal with this, but because I'm a dolt, + # we're going with this for now. + + #local xclip="xclip -selection clipboard" + local xclip="xclip" + before="$($xclip -o | base64)" + echo -n "$1" | $xclip + ( + sleep 45 + now="$($xclip -o | base64)" + if [[ $now != $(echo -n "$1" | base64) ]]; then + before="$now" + fi + + # It might be nice to programatically check to see if klipper exists, + # as well as checking for other common clipboard managers. But for now, + # this works fine -- if qdbus isn't there or if klipper isn't running, + # this essentially becomes a no-op. + # + # Clipboard managers frequently write their history out in plaintext, + # so we axe it here: + qdbus org.kde.klipper /klipper org.kde.klipper.klipper.clearClipboardHistory &>/dev/null + + echo "$before" | base64 -d | $xclip + ) & disown + echo "Copied $2 to clipboard. Will clear in 45 seconds." } # Load functions @@ -39,7 +41,7 @@ LIB="`dirname $0`/../functions" source "$LIB" || exit 1 # Check for xclip -if ! which xclip; then +if ! which xclip &> /dev/null; then echo "fatal: xclip not found" exit 1 fi diff --git a/lib/keyringer/completions/bash/keyringer b/lib/keyringer/completions/bash/keyringer index a640583..0f2cb2b 100644 --- a/lib/keyringer/completions/bash/keyringer +++ b/lib/keyringer/completions/bash/keyringer @@ -94,7 +94,7 @@ _keyringer() { recipients) opts="ls edit" ;; - ls|tree|mkdir|encrypt|encrypt-batch|decrypt|edit|append|append-batch|del|rm|recrypt|open|clip|xclip) + ls|tree|mkdir|encrypt|encrypt-batch|decrypt|edit|append|append-batch|del|rm|recrypt|open|clip|xclip|find) cur="`echo ${cur} | sed -e "s|^/*||"`" # avoid leading slash opts="$(bash -c "set -f && export KEYRINGER_CHECK_VERSION=false && keyringer $instance ls -p -d ${cur}*" 2> /dev/null)" ;; diff --git a/lib/keyringer/completions/zsh/_keyringer b/lib/keyringer/completions/zsh/_keyringer index 1a6d8c6..b4ccdd4 100644 --- a/lib/keyringer/completions/zsh/_keyringer +++ b/lib/keyringer/completions/zsh/_keyringer @@ -50,7 +50,7 @@ _keyringer() { recipients) compadd "$@" ls edit ;; - ls|tree|mkdir|encrypt|encrypt-batch|decrypt|edit|append|append-batch|del|rm|recrypt|open|clip|xclip) + ls|tree|mkdir|encrypt|encrypt-batch|decrypt|edit|append|append-batch|del|rm|recrypt|open|clip|xclip|find) words[4]="`echo $words[4] | sed -e "s|^/*||"`" # avoid leading slash compadd "$@" $(KEYRINGER_CHECK_VERSION=false keyringer $words[2] ls -p -d $words[4]'*' 2> /dev/null) ;; diff --git a/lib/keyringer/functions b/lib/keyringer/functions index 014c2c9..0b084af 100755 --- a/lib/keyringer/functions +++ b/lib/keyringer/functions @@ -301,6 +301,11 @@ function keyringer_set_env { # Upgrade configuration keyringer_upgrade + # Check repository integrity + if [ "$BASENAME" == "check" ]; then + keyringer_check_repository + fi + # Check recipients file keyringer_check_recipients $SUBCOMMAND @@ -403,6 +408,17 @@ function keyringer_upgrade { fi } +# Get an option +# +# Given that options are shared among users through the +# repository, we can't just "source $OPTIONS" as we would +# be opening a simple arbitrary code execution hole. +# +# TODO +function keyringer_get_option { + false +} + # Get a file argument function keyringer_get_file { FILE="$(keyringer_filename "$RELATIVE_PATH/$1")" @@ -413,11 +429,10 @@ function keyringer_get_file { elif [ ! -f "$KEYDIR/$FILE" ]; then # Try to find a similar file count=0 - candidates=(`keyringer_exec find "$BASEDIR" "$1" | grep -e '.asc$'`) + candidates=(`keyringer_exec find "$BASEDIR" | grep -i "$1" | grep -e '.asc$'`) if [ ! -z "$candidates" ]; then - echo "Could not find exact match \"$1\", please chose one" - echo "of the following secrets:" + echo "Could not find exact match \"$1\", please chose one of the following secrets:" echo "" for candidate in ${candidates[@]}; do @@ -426,7 +441,7 @@ function keyringer_get_file { done echo "" - read -p "Enter option: " option + read -p "Enter option (Ctrl-C to abort): " option if [[ "$option" =~ ^[0-9]+$ ]] && [ ! -z "${candidates[$option]}" ]; then FILE="$(keyringer_filename "$RELATIVE_PATH/${candidates[$option]}")" @@ -504,7 +519,7 @@ function keyringer_usage { printf "Keyringer $KEYRINGER_VERSION\n" printf "Usage: %s <keyring> <action> [arguments]\n\n" "$BASENAME" - printf "Available commands: \n\n" + printf "Available actions: \n\n" keyringer_show_actions | sed -e 's/^/\t/' # Display only when not in a keyring context @@ -514,12 +529,85 @@ function keyringer_usage { fi } +# Check repository integrity +function keyringer_check_repository { + # Check if it's a git repository + if [ ! -d "$BASEDIR/.git" ]; then + echo "Fatal: not a git repository: $BASEDIR" + exit 1 + fi + + # Git maintenance operations + echo "Running git maintenance operations..." + keyringer_exec git "$BASEDIR" fsck + keyringer_exec git "$BASEDIR" gc --prune=all + echo "" + + # Sync the repository + if [ "`keyringer_exec git "$BASEDIR" remote | wc -l`" != "0" ]; then + echo "Syncing git repository..." + keyringer_exec git "$BASEDIR" pull + echo "" + fi +} + +# Receive keys from keyservers +# TODO: gpg-maintenance trickery +# TODO: should be controlled by user preference +function keyringer_recv_keys { + local recipient="$1" + + echo "Trying to receive missing key $recipient..." + gpg --batch --recv-keys "$recipient" +} + +# Refresh keys from keyserver +# TODO: gpg-maintenance trickery +# TODO: should be controlled by user preference +function keyringer_refresh_keys { + local recipient="$1" + + echo "Trying to refresh key $recipient..." + gpg --batch --refresh-keys "$recipient" +} + +# Check recipient size +function keyringer_check_recipient_size { + local recipient="$1" + local 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 + fi +} + # Check recipients function keyringer_check_recipients { + # Shall we check recipients? if [ "$KEYRINGER_CHECK_RECIPIENTS" == "false" ]; then return fi + # Local variables + local processed=":" + # Check if recipients file is empty. if [ "`grep -vE "^#|^$" "$RECIPIENTS"/* | wc -l`" == 0 ] && [ "$SUBCOMMAND" != "edit" ]; then echo "Fatal: no recipients configured for this keyring." @@ -539,60 +627,98 @@ function keyringer_check_recipients { 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 + # Process a recipient just once + if echo $processed | grep -q "$recipient:"; then + continue + else + processed="$processed$recipient:" + fi -Please provide a full OpenPGP fingerprint, for example: + # Check recipient size + keyringer_check_recipient_size "$recipient" - john@doe.com ABCD1234ABCD12345678ABCD1234ABCD12345678 + # Check if key is present + keyringer_check_recipient_key "$recipient" -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. + # Refresh keys + if [ "$BASENAME" == "check" ] && [ "$refresh" != "no" ]; then + keyringer_refresh_keys "$recipient" + echo "" + fi -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 + # Check key expiration + keyringer_check_expiration "$recipient" + + done +} + +# Check if a key is present +function keyringer_check_recipient_key { + local recipient="$1" + + gpg --list-key "$recipient" &> /dev/null + if [ "$?" != "0" ]; then + if [ "$BASENAME" == "check" ]; then + refresh="no" + keyringer_recvs_keys "$recipient" + if [ "$?" != 0 ]; then + echo "Error fetching $recipient from keyservers." + continue fi + echo "" + else + echo "Fatal: no such key $recipient on your GPG keyring." + echo "Please check for this key or fix the recipient file." - # Current date - seconds="`date +%s`" + exit 1 + fi + fi +} - # Check the main key - expiry="`gpg --with-colons --fixed-list-mode --list-keys "$recipient" | grep ^pub | cut -d : -f 7`" +# Check key expiration +function keyringer_check_expiration { + # Variables + local recipient="$1" + local not_expired="0" - # Check if key is expired - if [ ! -z "$expiry" ] && [[ "$seconds" -gt "$expiry" ]]; then - echo "Fatal: primary key for $recipient expired on `date --date="@$expiry"`" - exit 1 - else - # Check the subkeys - for expiry in `gpg --with-colons --fixed-list-mode --list-keys "$recipient" | grep ^sub | cut -d : -f 7`; do - if [[ "$seconds" -lt "$expiry" ]]; then - not_expired="1" - fi + # Current date + seconds="`date +%s`" - if [ "$not_expired" != "1" ]; then - echo "Fatal: key $recipient has no keys suitable for encryption: all subkeys expired." - exit 1 - fi - done - fi + # Check the main key + expiry="`gpg --with-colons --fixed-list-mode --list-keys "$recipient" | grep ^pub | cut -d : -f 7`" + + # TODO: Time to expire can be configured via repository options. + ahead="$((86400 * 30 + $seconds))" + # Check if key is expired + if [ ! -z "$expiry" ] && [[ "$seconds" -gt "$expiry" ]]; then + echo "Fatal: primary key for $recipient expired on `date --date="@$expiry"`" + exit 1 + fi + + # Check if key is about to expire + # TODO: Users can be alerted by mail if configured by user preferences. + # TODO: Outgoing emails can be encrypted. + if [ "$BASENAME" == "check" ] && [ ! -z "$expiry" ] && [[ "$ahead" -gt "$expiry" ]]; then + echo "Warning: key $recipient will expire soon, on `date --date="@$expiry"`" + fi + + # Check the subkeys + for expiry in `gpg --with-colons --fixed-list-mode --list-keys "$recipient" | grep ^sub | cut -d : -f 7`; do + if [[ "$seconds" -lt "$expiry" ]]; then + not_expired="1" + fi + + if [[ "$ahead" -gt "$expiry" ]] && [ "$BASENAME" == "check" ]; then + echo "Warning: subkey from $recipient will expire soon, on `date --date="@$expiry"`" fi done + + # All subkeys are expired + if [ ! -z "$expiry" ] && [ "$not_expired" != "1" ]; then + echo "Fatal: key $recipient has no keys suitable for encryption: all subkeys expired." + exit 1 + fi } # Set recipients @@ -646,10 +772,35 @@ function keyringer_set_new_recipients { # Create a new recipients file function keyringer_create_new_recipients { - if [ ! -e "$1" ]; then - mkdir -p "`dirname $1`" + local recipients="$1" + local recipient + local key + local uid + local fpr + + if [ ! -e "$recipients" ]; then + mkdir -p "`dirname $recipients`" echo "# Use entries in the form of 'john@doe.com XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX'" > "$1" - echo "" >> "$1" + echo "" >> "$recipients" + + # Try to get an initial recipient + if [ -e "$HOME/.gnupg/gpg.conf" ]; then + recipient="`grep -e "^default-key" ~/.gnupg/gpg.conf | cut -d ' ' -f 2`" + + if [ ! -z "$recipient" ]; then + key="`gpg --fingerprint --with-colons $recipient 2> /dev/null`" + + if [ "$?" == "0" ]; then + fpr="`echo "$key" | grep -e '^fpr:' | head -1 | cut -d : -f 10`" + uid="`echo "$key" | grep -e '^uid:' | head -1 | cut -d : -f 10 | sed -e 's|^[^<]*<||' -e 's|>$||'`" + + if [ ! -z "$uid" ] && [ ! -z "$fpr" ]; then + echo "Default key $fpr ($uid) found at ~/.gnupg/gpg.conf, using as initial recipient." + echo "$uid $fpr" >> "$recipients" + fi + fi + fi + fi fi } |