password-store

Simple password manager using gpg and ordinary unix directories
git clone https://git.zx2c4.com/password-store
Log | Files | Refs | README | LICENSE

commit ebd57647fa54721ec64aba47c79819ad270b3a63
parent 73a39dafb0f2c0fde355bf62f3ac9e991c05dbb6
Author: Jason A. Donenfeld <Jason@zx2c4.com>
Date:   Fri, 18 Apr 2014 01:23:11 +0200

reencryption: only reencrypt files when required

Diffstat:
Mman/pass.1 | 12++++++------
Msrc/password-store.sh | 41+++++++++++++++++++++++++++++++----------
2 files changed, 37 insertions(+), 16 deletions(-)

diff --git a/man/pass.1 b/man/pass.1 @@ -126,19 +126,19 @@ alternatively named \fBremove\fP or \fBdelete\fP. If \fI--recursive\fP or \fI-r\ is specified, delete pass-name recursively if it is a directory. If \fI--force\fP or \fI-f\fP is specified, do not interactively prompt before removal. .TP -\fBmv\fP [ \fI--force\fP, \fI-f\fP ] \fIold-path\fP \fInew-path\fP +\fBmv\fP [ \fI--reencrypt\fP, \fI-e\fP ] [ \fI--force\fP, \fI-f\fP ] \fIold-path\fP \fInew-path\fP Renames the password or directory named \fIold-path\fP to \fInew-path\fP. This command is alternatively named \fBrename\fP. If \fI--force\fP is specified, silently overwrite \fInew-path\fP if it exists. If \fInew-path\fP ends in a -trailing \fI/\fP, it is always treated as a directory. Passwords will be reencrypted -to the corresponding keys of their new destination. +trailing \fI/\fP, it is always treated as a directory. Passwords may be optionally +reencrypted to the corresponding keys of their new destination. .TP -\fBcp\fP [ \fI--force\fP, \fI-f\fP ] \fIold-path\fP \fInew-path\fP +\fBcp\fP [ \fI--reencrypt\fP, \fI-e\fP ] [ \fI--force\fP, \fI-f\fP ] \fIold-path\fP \fInew-path\fP Copies the password or directory named \fIold-path\fP to \fInew-path\fP. This command is alternatively named \fBcopy\fP. If \fI--force\fP is specified, silently overwrite \fInew-path\fP if it exists. If \fInew-path\fP ends in a -trailing \fI/\fP, it is always treated as a directory. Passwords will be reencrypted -to the corresponding keys of their new destination. +trailing \fI/\fP, it is always treated as a directory. Passwords may be optionally +reencrypted to the corresponding keys of their new destination. .TP \fBgit\fP \fIgit-command-args\fP... If the password store is a git repository, pass \fIgit-command-args\fP as arguments to diff --git a/src/password-store.sh b/src/password-store.sh @@ -41,11 +41,13 @@ yesno() { } set_gpg_recipients() { GPG_RECIPIENT_ARGS=( ) + GPG_RECIPIENTS=( ) local gpg_id if [[ -n $PASSWORD_STORE_KEY ]]; then for gpg_id in $PASSWORD_STORE_KEY; do GPG_RECIPIENT_ARGS+=( "-r" "$gpg_id" ) + GPG_RECIPIENTS+=( "$gpg_id" ) done return fi @@ -69,6 +71,7 @@ set_gpg_recipients() { while read -r gpg_id; do GPG_RECIPIENT_ARGS+=( "-r" "$gpg_id" ) + GPG_RECIPIENTS+=( "$gpg_id" ) done < "$current" } agent_check() { @@ -84,15 +87,31 @@ agent_check() { reencrypt_path() { local passfile local passfile_dir + local passfile_display local fake_uniqueness_safety + local prev_gpg_recipients + local gpg_keys + local current_keys find "$1" -iname '*.gpg' | while read -r passfile; do fake_uniqueness_safety="$RANDOM" passfile_dir="${passfile%/*}" passfile_dir="${passfile_dir#$PREFIX}" passfile_dir="${passfile_dir#/}" + passfile_display="${passfile#$PREFIX/}" + passfile_display="${passfile_display%.gpg}" + set_gpg_recipients "$passfile_dir" - $GPG -d $GPG_OPTS "$passfile" | $GPG -e "${GPG_RECIPIENT_ARGS[@]}" -o "$passfile.new.$fake_uniqueness_safety" $GPG_OPTS && - mv -v "$passfile.new.$fake_uniqueness_safety" "$passfile" + [[ $prev_gpg_recipients != "${GPG_RECIPIENTS[@]}" ]] && \ + gpg_keys="$(gpg --list-keys --keyid-format long "${GPG_RECIPIENTS[@]}" | sed -n 's/sub *.*\/\([A-F0-9]\{16\}\) .*/\1/p' | sort | uniq)" + current_keys="$($GPG -v --list-only --keyid-format long "$passfile" 2>&1 | cut -d ' ' -f 5 | sort | uniq)" + + if [[ $gpg_keys != "$current_keys" ]]; then + echo "$passfile_display: reencrypting to ${gpg_keys//$'\n'/ }" + $GPG -d $GPG_OPTS "$passfile" | $GPG -e "${GPG_RECIPIENT_ARGS[@]}" -o "$passfile.new.$fake_uniqueness_safety" $GPG_OPTS && + mv "$passfile.new.$fake_uniqueness_safety" "$passfile" + fi + + prev_gpg_recipients="${GPG_RECIPIENTS[@]}" done } @@ -205,10 +224,10 @@ cmd_usage() { Prompt before overwriting existing password unless forced. $PROGRAM rm [--recursive,-r] [--force,-f] pass-name Remove existing password or directory, optionally forcefully. - $PROGRAM mv [--force,-f] old-path new-path - Renames or moves old-path to new-path, optionally forcefully. - $PROGRAM cp [--force,-f] old-path new-path - Copies old-path to new-path, optionally forcefully. + $PROGRAM mv [--reencrypt,-e] [--force,-f] old-path new-path + Renames or moves old-path to new-path, optionally forcefully, optionally reencrypting. + $PROGRAM cp [--reencrypt,-e] [--force,-f] old-path new-path + Copies old-path to new-path, optionally forcefully, optionally reencrypting. $PROGRAM git git-command-args... If the password store is a git repository, execute a git command specified by git-command-args. @@ -256,7 +275,7 @@ cmd_init() { if [[ $reencrypt -eq 1 ]]; then agent_check reencrypt_path "$PREFIX/$id_path" - git_add_file "$PREFIX/$id_path" "Reencrypted password store using new GPG id ${id_print}." + git_add_file "$PREFIX/$id_path" "Reencrypted password store using new GPG id ${id_print%, }." fi } @@ -517,16 +536,18 @@ cmd_copy_move() { shift local force=0 + local reencrypt=0 local opts opts="$($GETOPT -o f -l force -n "$PROGRAM" -- "$@")" local err=$? eval set -- "$opts" while true; do case $1 in -f|--force) force=1; shift ;; + -e|--reencrypt) reencrypt=1; shift ;; --) shift; break ;; esac done if [[ $# -ne 2 ]]; then - echo "Usage: $PROGRAM $COMMAND [--force,-f] old-path new-path" + echo "Usage: $PROGRAM $COMMAND [--reencrypt,-e] [--force,-f] old-path new-path" exit 1 fi local old_path="$PREFIX/${1%/}" @@ -550,7 +571,7 @@ cmd_copy_move() { if [[ $move -eq 1 ]]; then mv $interactive -v "$old_path" "$new_path" || exit 1 - [[ -e "$new_path" ]] && reencrypt_path "$new_path" + [[ $reencrypt -eq 1 && -e "$new_path" ]] && reencrypt_path "$new_path" if [[ -d $GIT_DIR && ! -e $old_path ]]; then git rm -qr "$old_path" @@ -562,7 +583,7 @@ cmd_copy_move() { done else cp $interactive -r -v "$old_path" "$new_path" || exit 1 - [[ -e "$new_path" ]] && reencrypt_path "$new_path" + [[ $reencrypt -eq 1 && -e "$new_path" ]] && reencrypt_path "$new_path" git_add_file "$new_path" "Copied ${1} to ${2}." fi }