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 586f6bd1eb32766168645d0582839dbf36e7f205
parent 2ba85daddf46422aa510a8c03ff75ba59761c190
Author: Jason A. Donenfeld <Jason@zx2c4.com>
Date:   Thu, 17 Apr 2014 23:04:59 +0200

move/copy: always reencrypt passwords at destination

Diffstat:
Mman/pass.1 | 10+++++++++-
Msrc/completion/pass.bash-completion | 4++--
Msrc/completion/pass.fish-completion | 4++++
Msrc/completion/pass.zsh-completion | 3++-
Msrc/password-store.sh | 60+++++++++++++++++++++++++++++++++++++++---------------------
5 files changed, 56 insertions(+), 25 deletions(-)

diff --git a/man/pass.1 b/man/pass.1 @@ -130,7 +130,15 @@ or \fI-f\fP is specified, do not interactively prompt before removal. 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. +trailing \fI/\fP, it is always treated as a directory. Passwords will be reencrypted +to the corresponding keys of their new destination. +.TP +\fBcp\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. .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/completion/pass.bash-completion b/src/completion/pass.bash-completion @@ -57,7 +57,7 @@ _pass() { COMPREPLY=() local cur="${COMP_WORDS[COMP_CWORD]}" - local commands="init ls find grep show insert generate edit rm git help version" + local commands="init ls find grep show insert generate edit rm mv cp git help version" if [[ $COMP_CWORD -gt 1 ]]; then local lastarg="${COMP_WORDS[$COMP_CWORD-1]}" case "${COMP_WORDS[1]}" in @@ -84,7 +84,7 @@ _pass() COMPREPLY+=($(compgen -W "-n --no-symbols -c --clip -f --force" -- ${cur})) _pass_complete_entries ;; - mv|rename) + cp|copy|mv|rename) COMPREPLY+=($(compgen -W "-f --force" -- ${cur})) _pass_complete_entries ;; diff --git a/src/completion/pass.fish-completion b/src/completion/pass.fish-completion @@ -85,6 +85,10 @@ complete -c $PROG -f -A -n '__fish_pass_needs_command' -a mv -d 'Command: rename complete -c $PROG -f -A -n '__fish_pass_uses_command mv' -s f -l force -d 'Force rename' complete -c $PROG -f -A -n '__fish_pass_uses_command mv' -a "(__fish_pass_print_entries_and_dirs)" +complete -c $PROG -f -A -n '__fish_pass_needs_command' -a cp -d 'Command: copy existing password' +complete -c $PROG -f -A -n '__fish_pass_uses_command cp' -s f -l force -d 'Force copy' +complete -c $PROG -f -A -n '__fish_pass_uses_command cp' -a "(__fish_pass_print_entries_and_dirs)" + complete -c $PROG -f -A -n '__fish_pass_needs_command' -a rm -d 'Command: remove existing password' complete -c $PROG -f -A -n '__fish_pass_uses_command rm' -s r -l recursive -d 'Remove password groups recursively' complete -c $PROG -f -A -n '__fish_pass_uses_command rm' -s f -l force -d 'Force removal' diff --git a/src/completion/pass.zsh-completion b/src/completion/pass.zsh-completion @@ -48,7 +48,7 @@ _pass () { "--clip[copy password to the clipboard]" _pass_complete_entries_with_subdirs ;; - mv|rename) + cp|copy|mv|rename) _arguments : \ "-f[force rename]" \ "--force[force rename]" \ @@ -90,6 +90,7 @@ _pass () { "generate:Generate a new password using pwgen" "edit:Edit a password with \$EDITOR" "mv:Rename the password" + "cp:Copy the password" "rm:Remove the password" "git:Call git on the password store" "version:Output version information" diff --git a/src/password-store.sh b/src/password-store.sh @@ -80,6 +80,20 @@ agent_check() { _EOF )" } +reencrypt_path() { + local passfile + local passfile_dir + local fake_uniqueness_safety + 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#/}" + 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" + done +} # # END helper functions @@ -192,6 +206,8 @@ cmd_usage() { 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 cv [--force,-f] old-path new-path + Copies old-path to new-path, optionally forcefully. $PROGRAM git git-command-args... If the password store is a git repository, execute a git command specified by git-command-args. @@ -238,16 +254,7 @@ cmd_init() { if [[ $reencrypt -eq 1 ]]; then agent_check - local passfile - find "$PREFIX/$id_path" -iname '*.gpg' | while read -r passfile; do - fake_uniqueness_safety="$RANDOM" - passfile_dir="${passfile%/*}" - passfile_dir="${passfile_dir#$PREFIX}" - passfile_dir="${passfile_dir#/}" - 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" - done + reencrypt_path "$PREFIX/$id_path" git_add_file "$PREFIX/$id_path" "Reencrypted password store using new GPG id ${id_print}." fi } @@ -503,9 +510,12 @@ cmd_delete() { fi } -cmd_rename() { - local force=0 +cmd_copy_move() { + local move=1 + [[ $1 == "copy" ]] && move=0 + shift + local force=0 local opts opts="$($GETOPT -o f -l force -n "$PROGRAM" -- "$@")" local err=$? @@ -537,16 +547,23 @@ cmd_rename() { local interactive="-i" [[ $force -eq 1 ]] && interactive="-f" - mv $interactive -v "$old_path" "$new_path" || exit 1 + if [[ $move -eq 1 ]]; then + mv $interactive -v "$old_path" "$new_path" || exit 1 + [[ -e "$new_path" ]] && reencrypt_path "$new_path" - if [[ -d $GIT_DIR && ! -e $old_path ]]; then - git rm -qr "$old_path" - git_add_file "$new_path" "Renamed ${1} to ${2}." - fi + if [[ -d $GIT_DIR && ! -e $old_path ]]; then + git rm -qr "$old_path" + git_add_file "$new_path" "Renamed ${1} to ${2}." + fi - while rmdir "$old_dir" &>/dev/null; do - old_dir="${old_dir%/*}" - done + while rmdir "$old_dir" &>/dev/null; do + old_dir="${old_dir%/*}" + done + else + cp $interactive -r -v "$old_path" "$new_path" || exit 1 + [[ -e "$new_path" ]] && reencrypt_path "$new_path" + git_add_file "$new_path" "Copied ${1} to ${2}." + fi } cmd_git() { @@ -579,7 +596,8 @@ case "$1" in edit) shift; cmd_edit "$@"; ;; generate) shift; cmd_generate "$@"; ;; delete|rm|remove) shift; cmd_delete "$@"; ;; - rename|mv) shift; cmd_rename "$@"; ;; + rename|mv) shift; cmd_copy_move "move" "$@"; ;; + copy|cp) shift; cmd_copy_move "copy" "$@"; ;; git) shift; cmd_git "$@"; ;; *) COMMAND="show"; cmd_show "$@"; ;; esac