El día martes, febrero 27, 2024 a las 08:52:55 -0600, Jacob Bachmeyer via 
Gnupg-users escribió:

> > It says:
> > 
> > purism@pureos:~$ gpg --list-keys
> > /home/purism/.gnupg/pubring.kbx
> > -------------------------------
> > pub   rsa2048 2021-10-30 [SC]
> >       336EB96892FE9FE7F6...................
> > uid           [ultimate] Matthias Apitz (GnuPG CCID L5) <g...@unixarea.de>
> > sub   rsa2048 2021-10-30 [A]
> > sub   rsa2048 2021-10-30 [E]
> > 
> > [...]
> 
> Are you sure that *that* is the list of public keys used by pass(1)?  It
> almost certainly is not, since GPG's public key collection is meant to
> collect keys for a variety of uses.  For example, sending encrypted emails
> or verifying signatures.  You probably do not want your password store
> encrypted to everyone you correspond with!
> 
> Therefore, pass(1) almost certainly has its own list of keys stored
> somewhere else.  Your regular public key was probably copied to that list
> when you initialized the password store.  That is the list that you need to
> regularly check, lest Mallory be able to sneak his key onto it.  That list
> is *also* where you need to add your new public key in order to migrate your
> password store.
> 
> ...

It must be *that* list pass(1) is using, because:

purism@pureos:~$ ls -ld .gnu*
drwx------ 5 purism purism 4096 Feb 28 05:59 .gnupg

purism@pureos:~$ env | grep GNU
GNUPGHOME=/home/purism/.gnupg

purism@pureos:~$ file .password-store/test.gpg
.password-store/test.gpg: PGP RSA encrypted session key - keyid: 39BDCE02 
5E4698B6 RSA (Encrypt or Sign) 2048b .

purism@pureos:~$ gpg -da .password-store/test.gpg
(it ask for the card's PIN on the L5 display desktop)
gpg: encrypted with 2048-bit RSA key, ID 39BDCE025E4698B6, created 2021-10-30
      "Matthias Apitz (GnuPG CCID L5) <g...@unixarea.de>"
secret
purism@pureos:~$ cat .password-store/.gpg-id
CCID L5

I'm attaching the shell script /usr/bin/pass; the code for the "init"
command of pass(1) starts at line 300 and I don't see that any other key
is used then the one in GNUPGHOME.

If I understand this correctly if any other public key would be added to
the file /home/purism/.gnupg/pubring.kbx, pass(1) would only use the key
"CCID L5" to encrypt any new object stored in ~/.password-store and not
the public key of Mallory. Am I wrong?

I will consider your hints about RSA4096 when initializing the new second
card. Thanks for them.

        matthias




-- 
Matthias Apitz, ✉ g...@unixarea.de, http://www.unixarea.de/ +49-176-38902045
Public GnuPG key: http://www.unixarea.de/key.pub

I am not at war with Russia.  Я не воюю с Россией.
Ich bin nicht im Krieg mit Russland.
#!/usr/bin/env bash

# Copyright (C) 2012 - 2018 Jason A. Donenfeld <ja...@zx2c4.com>. All Rights 
Reserved.
# This file is licensed under the GPLv2+. Please see COPYING for more 
information.

umask "${PASSWORD_STORE_UMASK:-077}"
set -o pipefail

GPG_OPTS=( $PASSWORD_STORE_GPG_OPTS "--quiet" "--yes" "--compress-algo=none" 
"--no-encrypt-to" )
GPG="gpg"
export GPG_TTY="${GPG_TTY:-$(tty 2>/dev/null)}"
which gpg2 &>/dev/null && GPG="gpg2"
[[ -n $GPG_AGENT_INFO || $GPG == "gpg2" ]] && GPG_OPTS+=( "--batch" 
"--use-agent" )

PREFIX="${PASSWORD_STORE_DIR:-$HOME/.password-store}"
EXTENSIONS="${PASSWORD_STORE_EXTENSIONS_DIR:-$PREFIX/.extensions}"
X_SELECTION="${PASSWORD_STORE_X_SELECTION:-clipboard}"
CLIP_TIME="${PASSWORD_STORE_CLIP_TIME:-45}"
GENERATED_LENGTH="${PASSWORD_STORE_GENERATED_LENGTH:-25}"
CHARACTER_SET="${PASSWORD_STORE_CHARACTER_SET:-[:graph:]}"
CHARACTER_SET_NO_SYMBOLS="${PASSWORD_STORE_CHARACTER_SET_NO_SYMBOLS:-[:alnum:]}"

export GIT_CEILING_DIRECTORIES="$PREFIX/.."

#
# BEGIN helper functions
#

set_git() {
        INNER_GIT_DIR="${1%/*}"
        while [[ ! -d $INNER_GIT_DIR && ${INNER_GIT_DIR%/*}/ == "${PREFIX%/}/"* 
]]; do
                INNER_GIT_DIR="${INNER_GIT_DIR%/*}"
        done
        [[ $(git -C "$INNER_GIT_DIR" rev-parse --is-inside-work-tree 
2>/dev/null) == true ]] || INNER_GIT_DIR=""
}
git_add_file() {
        [[ -n $INNER_GIT_DIR ]] || return
        git -C "$INNER_GIT_DIR" add "$1" || return
        [[ -n $(git -C "$INNER_GIT_DIR" status --porcelain "$1") ]] || return
        git_commit "$2"
}
git_commit() {
        local sign=""
        [[ -n $INNER_GIT_DIR ]] || return
        [[ $(git -C "$INNER_GIT_DIR" config --bool --get pass.signcommits) == 
"true" ]] && sign="-S"
        git -C "$INNER_GIT_DIR" commit $sign -m "$1"
}
yesno() {
        [[ -t 0 ]] || return 0
        local response
        read -r -p "$1 [y/N] " response
        [[ $response == [yY] ]] || exit 1
}
die() {
        echo "$@" >&2
        exit 1
}
verify_file() {
        [[ -n $PASSWORD_STORE_SIGNING_KEY ]] || return 0
        [[ -f $1.sig ]] || die "Signature for $1 does not exist."
        local fingerprints="$($GPG $PASSWORD_STORE_GPG_OPTS --verify 
--status-fd=1 "$1.sig" "$1" 2>/dev/null | sed -n 's/^\[GNUPG:\] VALIDSIG 
\([A-F0-9]\{40\}\) .* \([A-F0-9]\{40\}\)$/\1\n\2/p')"
        local fingerprint found=0
        for fingerprint in $PASSWORD_STORE_SIGNING_KEY; do
                [[ $fingerprint =~ ^[A-F0-9]{40}$ ]] || continue
                [[ $fingerprints == *$fingerprint* ]] && { found=1; break; }
        done
        [[ $found -eq 1 ]] || die "Signature for $1 is invalid."
}
set_gpg_recipients() {
        GPG_RECIPIENT_ARGS=( )
        GPG_RECIPIENTS=( )

        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

        local current="$PREFIX/$1"
        while [[ $current != "$PREFIX" && ! -f $current/.gpg-id ]]; do
                current="${current%/*}"
        done
        current="$current/.gpg-id"

        if [[ ! -f $current ]]; then
                cat >&2 <<-_EOF
                Error: You must run:
                    $PROGRAM init your-gpg-id
                before you may use the password store.

                _EOF
                cmd_usage
                exit 1
        fi

        verify_file "$current"

        local gpg_id
        while read -r gpg_id; do
                GPG_RECIPIENT_ARGS+=( "-r" "$gpg_id" )
                GPG_RECIPIENTS+=( "$gpg_id" )
        done < "$current"
}

reencrypt_path() {
        local prev_gpg_recipients="" gpg_keys="" current_keys="" index passfile
        local groups="$($GPG $PASSWORD_STORE_GPG_OPTS --list-config 
--with-colons | grep "^cfg:group:.*")"
        while read -r -d "" passfile; do
                local passfile_dir="${passfile%/*}"
                passfile_dir="${passfile_dir#$PREFIX}"
                passfile_dir="${passfile_dir#/}"
                local passfile_display="${passfile#$PREFIX/}"
                passfile_display="${passfile_display%.gpg}"
                local 
passfile_temp="${passfile}.tmp.${RANDOM}.${RANDOM}.${RANDOM}.${RANDOM}.--"

                set_gpg_recipients "$passfile_dir"
                if [[ $prev_gpg_recipients != "${GPG_RECIPIENTS[*]}" ]]; then
                        for index in "${!GPG_RECIPIENTS[@]}"; do
                                local group="$(sed -n "s/^cfg:group:$(sed 
's/[\/&]/\\&/g' <<<"${GPG_RECIPIENTS[$index]}"):\\(.*\\)\$/\\1/p" <<<"$groups" 
| head -n 1)"
                                [[ -z $group ]] && continue
                                IFS=";" eval 'GPG_RECIPIENTS+=( $group )' # 
http://unix.stackexchange.com/a/92190
                                unset "GPG_RECIPIENTS[$index]"
                        done
                        gpg_keys="$($GPG $PASSWORD_STORE_GPG_OPTS --list-keys 
--with-colons "${GPG_RECIPIENTS[@]}" | sed -n 
's/^sub:[^:]*:[^:]*:[^:]*:\([^:]*\):[^:]*:[^:]*:[^:]*:[^:]*:[^:]*:[^:]*:[a-zA-Z]*e[a-zA-Z]*:.*/\1/p'
 | LC_ALL=C sort -u)"
                fi
                current_keys="$(LC_ALL=C $GPG $PASSWORD_STORE_GPG_OPTS -v 
--no-secmem-warning --no-permission-warning --decrypt --list-only 
--keyid-format long "$passfile" 2>&1 | sed -n 's/^gpg: public key is 
\([A-F0-9]\+\)$/\1/p' | LC_ALL=C sort -u)"

                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_temp" "${GPG_OPTS[@]}" &&
                        mv "$passfile_temp" "$passfile" || rm -f 
"$passfile_temp"
                fi
                prev_gpg_recipients="${GPG_RECIPIENTS[*]}"
        done < <(find "$1" -path '*/.git' -prune -o -iname '*.gpg' -print0)
}
check_sneaky_paths() {
        local path
        for path in "$@"; do
                [[ $path =~ /\.\.$ || $path =~ ^\.\./ || $path =~ /\.\./ || 
$path =~ ^\.\.$ ]] && die "Error: You've attempted to pass a sneaky path to 
pass. Go home."
        done
}

#
# END helper functions
#

#
# BEGIN platform definable
#

clip() {
        # This base64 business is because bash cannot store binary data in a 
shell
        # variable. Specifically, it cannot store nulls nor (non-trivally) store
        # trailing new lines.
        local sleep_argv0="password store sleep on display $DISPLAY"
        pkill -f "^$sleep_argv0" 2>/dev/null && sleep 0.5
        local before="$(xclip -o -selection "$X_SELECTION" 2>/dev/null | 
$BASE64)"
        echo -n "$1" | xclip -selection "$X_SELECTION" || die "Error: Could not 
copy data to the clipboard"
        (
                ( exec -a "$sleep_argv0" bash <<<"trap 'kill %1' TERM; sleep 
'$CLIP_TIME' & wait" )
                local now="$(xclip -o -selection "$X_SELECTION" | $BASE64)"
                [[ $now != $(echo -n "$1" | $BASE64) ]] && before="$now"

                # 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 "$X_SELECTION"
        ) >/dev/null 2>&1 & disown
        echo "Copied $2 to clipboard. Will clear in $CLIP_TIME seconds."
}

qrcode() {
        if [[ -n $DISPLAY || -n $WAYLAND_DISPLAY ]]; then
                if type feh >/dev/null 2>&1; then
                        echo -n "$1" | qrencode --size 10 -o - | feh -x --title 
"pass: $2" -g +200+200 -
                        return
                elif type gm >/dev/null 2>&1; then
                        echo -n "$1" | qrencode --size 10 -o - | gm display 
-title "pass: $2" -geometry +200+200 -
                        return
                elif type display >/dev/null 2>&1; then
                        echo -n "$1" | qrencode --size 10 -o - | display -title 
"pass: $2" -geometry +200+200 -
                        return
                fi
        fi
        echo -n "$1" | qrencode -t utf8
}

tmpdir() {
        [[ -n $SECURE_TMPDIR ]] && return
        local warn=1
        [[ $1 == "nowarn" ]] && warn=0
        local template="$PROGRAM.XXXXXXXXXXXXX"
        if [[ -d /dev/shm && -w /dev/shm && -x /dev/shm ]]; then
                SECURE_TMPDIR="$(mktemp -d "/dev/shm/$template")"
                remove_tmpfile() {
                        rm -rf "$SECURE_TMPDIR"
                }
                trap remove_tmpfile EXIT
        else
                [[ $warn -eq 1 ]] && yesno "$(cat <<-_EOF
                Your system does not have /dev/shm, which means that it may
                be difficult to entirely erase the temporary non-encrypted
                password file after editing.

                Are you sure you would like to continue?
                _EOF
                )"
                SECURE_TMPDIR="$(mktemp -d "${TMPDIR:-/tmp}/$template")"
                shred_tmpfile() {
                        find "$SECURE_TMPDIR" -type f -exec $SHRED {} +
                        rm -rf "$SECURE_TMPDIR"
                }
                trap shred_tmpfile EXIT
        fi

}
GETOPT="getopt"
SHRED="shred -f -z"
BASE64="base64"


#
# END platform definable
#


#
# BEGIN subcommand functions
#

cmd_version() {
        cat <<-_EOF
        ============================================
        = pass: the standard unix password manager =
        =                                          =
        =                  v1.7.3                  =
        =                                          =
        =             Jason A. Donenfeld           =
        =               ja...@zx2c4.com            =
        =                                          =
        =      http://www.passwordstore.org/       =
        ============================================
        _EOF
}

cmd_usage() {
        cmd_version
        echo
        cat <<-_EOF
        Usage:
            $PROGRAM init [--path=subfolder,-p subfolder] gpg-id...
                Initialize new password storage and use gpg-id for encryption.
                Selectively reencrypt existing passwords using new gpg-id.
            $PROGRAM [ls] [subfolder]
                List passwords.
            $PROGRAM find pass-names...
                List passwords that match pass-names.
            $PROGRAM [show] [--clip[=line-number],-c[line-number]] pass-name
                Show existing password and optionally put it on the clipboard.
                If put on the clipboard, it will be cleared in $CLIP_TIME 
seconds.
            $PROGRAM grep [GREPOPTIONS] search-string
                Search for password files containing search-string when 
decrypted.
            $PROGRAM insert [--echo,-e | --multiline,-m] [--force,-f] pass-name
                Insert new password. Optionally, echo the password back to the 
console
                during entry. Or, optionally, the entry may be multiline. 
Prompt before
                overwriting existing password unless forced.
            $PROGRAM edit pass-name
                Insert a new password or edit an existing password using 
${EDITOR:-editor}.
            $PROGRAM generate [--no-symbols,-n] [--clip,-c] [--in-place,-i | 
--force,-f] pass-name [pass-length]
                Generate a new password of pass-length (or $GENERATED_LENGTH if 
unspecified) with optionally no symbols.
                Optionally put it on the clipboard and clear board after 
$CLIP_TIME seconds.
                Prompt before overwriting existing password unless forced.
                Optionally replace only the first line of an existing file with 
a new password.
            $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, 
selectively reencrypting.
            $PROGRAM cp [--force,-f] old-path new-path
                Copies old-path to new-path, optionally forcefully, selectively 
reencrypting.
            $PROGRAM git git-command-args...
                If the password store is a git repository, execute a git command
                specified by git-command-args.
            $PROGRAM help
                Show this text.
            $PROGRAM version
                Show version information.

        More information may be found in the pass(1) man page.
        _EOF
}

cmd_init() {
        local opts id_path=""
        opts="$($GETOPT -o p: -l path: -n "$PROGRAM" -- "$@")"
        local err=$?
        eval set -- "$opts"
        while true; do case $1 in
                -p|--path) id_path="$2"; shift 2 ;;
                --) shift; break ;;
        esac done

        [[ $err -ne 0 || $# -lt 1 ]] && die "Usage: $PROGRAM $COMMAND 
[--path=subfolder,-p subfolder] gpg-id..."
        [[ -n $id_path ]] && check_sneaky_paths "$id_path"
        [[ -n $id_path && ! -d $PREFIX/$id_path && -e $PREFIX/$id_path ]] && 
die "Error: $PREFIX/$id_path exists but is not a directory."

        local gpg_id="$PREFIX/$id_path/.gpg-id"
        set_git "$gpg_id"

        if [[ $# -eq 1 && -z $1 ]]; then
                [[ ! -f "$gpg_id" ]] && die "Error: $gpg_id does not exist and 
so cannot be removed."
                rm -v -f "$gpg_id" || exit 1
                if [[ -n $INNER_GIT_DIR ]]; then
                        git -C "$INNER_GIT_DIR" rm -qr "$gpg_id"
                        git_commit "Deinitialize ${gpg_id}${id_path:+ 
($id_path)}."
                fi
                rmdir -p "${gpg_id%/*}" 2>/dev/null
        else
                mkdir -v -p "$PREFIX/$id_path"
                printf "%s\n" "$@" > "$gpg_id"
                local id_print="$(printf "%s, " "$@")"
                echo "Password store initialized for ${id_print%, }${id_path:+ 
($id_path)}"
                git_add_file "$gpg_id" "Set GPG id to ${id_print%, }${id_path:+ 
($id_path)}."
                if [[ -n $PASSWORD_STORE_SIGNING_KEY ]]; then
                        local signing_keys=( ) key
                        for key in $PASSWORD_STORE_SIGNING_KEY; do
                                signing_keys+=( --default-key $key )
                        done
                        $GPG "${GPG_OPTS[@]}" "${signing_keys[@]}" 
--detach-sign "$gpg_id" || die "Could not sign .gpg_id."
                        key="$($GPG --verify --status-fd=1 "$gpg_id.sig" 
"$gpg_id" 2>/dev/null | sed -n 's/^\[GNUPG:\] VALIDSIG [A-F0-9]\{40\} .* 
\([A-F0-9]\{40\}\)$/\1/p')"
                        [[ -n $key ]] || die "Signing of .gpg_id unsuccessful."
                        git_add_file "$gpg_id.sig" "Signing new GPG id with 
${key//[$IFS]/,}."
                fi
        fi

        reencrypt_path "$PREFIX/$id_path"
        git_add_file "$PREFIX/$id_path" "Reencrypt password store using new GPG 
id ${id_print%, }${id_path:+ ($id_path)}."
}

cmd_show() {
        local opts selected_line clip=0 qrcode=0
        opts="$($GETOPT -o q::c:: -l qrcode::,clip:: -n "$PROGRAM" -- "$@")"
        local err=$?
        eval set -- "$opts"
        while true; do case $1 in
                -q|--qrcode) qrcode=1; selected_line="${2:-1}"; shift 2 ;;
                -c|--clip) clip=1; selected_line="${2:-1}"; shift 2 ;;
                --) shift; break ;;
        esac done

        [[ $err -ne 0 || ( $qrcode -eq 1 && $clip -eq 1 ) ]] && die "Usage: 
$PROGRAM $COMMAND [--clip[=line-number],-c[line-number]] 
[--qrcode[=line-number],-q[line-number]] [pass-name]"

        local pass
        local path="$1"
        local passfile="$PREFIX/$path.gpg"
        check_sneaky_paths "$path"
        if [[ -f $passfile ]]; then
                if [[ $clip -eq 0 && $qrcode -eq 0 ]]; then
                        pass="$($GPG -d "${GPG_OPTS[@]}" "$passfile" | 
$BASE64)" || exit $?
                        echo "$pass" | $BASE64 -d
                else
                        [[ $selected_line =~ ^[0-9]+$ ]] || die "Clip location 
'$selected_line' is not a number."
                        pass="$($GPG -d "${GPG_OPTS[@]}" "$passfile" | tail -n 
+${selected_line} | head -n 1)" || exit $?
                        [[ -n $pass ]] || die "There is no password to put on 
the clipboard at line ${selected_line}."
                        if [[ $clip -eq 1 ]]; then
                                clip "$pass" "$path"
                        elif [[ $qrcode -eq 1 ]]; then
                                qrcode "$pass" "$path"
                        fi
                fi
        elif [[ -d $PREFIX/$path ]]; then
                if [[ -z $path ]]; then
                        echo "Password Store"
                else
                        echo "${path%\/}"
                fi
                tree -C -l --noreport "$PREFIX/$path" | tail -n +2 | sed -E 
's/\.gpg(\x1B\[[0-9]+m)?( ->|$)/\1\2/g' # remove .gpg at end of line, but keep 
colors
        elif [[ -z $path ]]; then
                die "Error: password store is empty. Try \"pass init\"."
        else
                die "Error: $path is not in the password store."
        fi
}

cmd_find() {
        [[ $# -eq 0 ]] && die "Usage: $PROGRAM $COMMAND pass-names..."
        IFS="," eval 'echo "Search Terms: $*"'
        local terms="*$(printf '%s*|*' "$@")"
        tree -C -l --noreport -P "${terms%|*}" --prune --matchdirs 
--ignore-case "$PREFIX" | tail -n +2 | sed -E 's/\.gpg(\x1B\[[0-9]+m)?( 
->|$)/\1\2/g'
}

cmd_grep() {
        [[ $# -lt 1 ]] && die "Usage: $PROGRAM $COMMAND [GREPOPTIONS] 
search-string"
        local passfile grepresults
        while read -r -d "" passfile; do
                grepresults="$($GPG -d "${GPG_OPTS[@]}" "$passfile" | grep 
--color=always "$@")"
                [[ $? -ne 0 ]] && continue
                passfile="${passfile%.gpg}"
                passfile="${passfile#$PREFIX/}"
                local passfile_dir="${passfile%/*}/"
                [[ $passfile_dir == "${passfile}/" ]] && passfile_dir=""
                passfile="${passfile##*/}"
                printf "\e[94m%s\e[1m%s\e[0m:\n" "$passfile_dir" "$passfile"
                echo "$grepresults"
        done < <(find -L "$PREFIX" -path '*/.git' -prune -o -iname '*.gpg' 
-print0)
}

cmd_insert() {
        local opts multiline=0 noecho=1 force=0
        opts="$($GETOPT -o mef -l multiline,echo,force -n "$PROGRAM" -- "$@")"
        local err=$?
        eval set -- "$opts"
        while true; do case $1 in
                -m|--multiline) multiline=1; shift ;;
                -e|--echo) noecho=0; shift ;;
                -f|--force) force=1; shift ;;
                --) shift; break ;;
        esac done

        [[ $err -ne 0 || ( $multiline -eq 1 && $noecho -eq 0 ) || $# -ne 1 ]] 
&& die "Usage: $PROGRAM $COMMAND [--echo,-e | --multiline,-m] [--force,-f] 
pass-name"
        local path="${1%/}"
        local passfile="$PREFIX/$path.gpg"
        check_sneaky_paths "$path"
        set_git "$passfile"

        [[ $force -eq 0 && -e $passfile ]] && yesno "An entry already exists 
for $path. Overwrite it?"

        mkdir -p -v "$PREFIX/$(dirname -- "$path")"
        set_gpg_recipients "$(dirname -- "$path")"

        if [[ $multiline -eq 1 ]]; then
                echo "Enter contents of $path and press Ctrl+D when finished:"
                echo
                $GPG -e "${GPG_RECIPIENT_ARGS[@]}" -o "$passfile" 
"${GPG_OPTS[@]}" || die "Password encryption aborted."
        elif [[ $noecho -eq 1 ]]; then
                local password password_again
                while true; do
                        read -r -p "Enter password for $path: " -s password || 
exit 1
                        echo
                        read -r -p "Retype password for $path: " -s 
password_again || exit 1
                        echo
                        if [[ $password == "$password_again" ]]; then
                                echo "$password" | $GPG -e 
"${GPG_RECIPIENT_ARGS[@]}" -o "$passfile" "${GPG_OPTS[@]}" || die "Password 
encryption aborted."
                                break
                        else
                                die "Error: the entered passwords do not match."
                        fi
                done
        else
                local password
                read -r -p "Enter password for $path: " -e password
                echo "$password" | $GPG -e "${GPG_RECIPIENT_ARGS[@]}" -o 
"$passfile" "${GPG_OPTS[@]}" || die "Password encryption aborted."
        fi
        git_add_file "$passfile" "Add given password for $path to store."
}

cmd_edit() {
        [[ $# -ne 1 ]] && die "Usage: $PROGRAM $COMMAND pass-name"

        local path="${1%/}"
        check_sneaky_paths "$path"
        mkdir -p -v "$PREFIX/$(dirname -- "$path")"
        set_gpg_recipients "$(dirname -- "$path")"
        local passfile="$PREFIX/$path.gpg"
        set_git "$passfile"

        tmpdir #Defines $SECURE_TMPDIR
        local tmp_file="$(mktemp -u "$SECURE_TMPDIR/XXXXXX")-${path//\//-}.txt"

        local action="Add"
        if [[ -f $passfile ]]; then
                $GPG -d -o "$tmp_file" "${GPG_OPTS[@]}" "$passfile" || exit 1
                action="Edit"
        fi
        ${EDITOR:-editor} "$tmp_file"
        [[ -f $tmp_file ]] || die "New password not saved."
        $GPG -d -o - "${GPG_OPTS[@]}" "$passfile" 2>/dev/null | diff - 
"$tmp_file" &>/dev/null && die "Password unchanged."
        while ! $GPG -e "${GPG_RECIPIENT_ARGS[@]}" -o "$passfile" 
"${GPG_OPTS[@]}" "$tmp_file"; do
                yesno "GPG encryption failed. Would you like to try again?"
        done
        git_add_file "$passfile" "$action password for $path using 
${EDITOR:-editor}."
}

cmd_generate() {
        local opts qrcode=0 clip=0 force=0 characters="$CHARACTER_SET" 
inplace=0 pass
        opts="$($GETOPT -o nqcif -l no-symbols,qrcode,clip,in-place,force -n 
"$PROGRAM" -- "$@")"
        local err=$?
        eval set -- "$opts"
        while true; do case $1 in
                -n|--no-symbols) characters="$CHARACTER_SET_NO_SYMBOLS"; shift 
;;
                -q|--qrcode) qrcode=1; shift ;;
                -c|--clip) clip=1; shift ;;
                -f|--force) force=1; shift ;;
                -i|--in-place) inplace=1; shift ;;
                --) shift; break ;;
        esac done

        [[ $err -ne 0 || ( $# -ne 2 && $# -ne 1 ) || ( $force -eq 1 && $inplace 
-eq 1 ) || ( $qrcode -eq 1 && $clip -eq 1 ) ]] && die "Usage: $PROGRAM $COMMAND 
[--no-symbols,-n] [--clip,-c] [--qrcode,-q] [--in-place,-i | --force,-f] 
pass-name [pass-length]"
        local path="$1"
        local length="${2:-$GENERATED_LENGTH}"
        check_sneaky_paths "$path"
        [[ $length =~ ^[0-9]+$ ]] || die "Error: pass-length \"$length\" must 
be a number."
        [[ $length -gt 0 ]] || die "Error: pass-length must be greater than 
zero."
        mkdir -p -v "$PREFIX/$(dirname -- "$path")"
        set_gpg_recipients "$(dirname -- "$path")"
        local passfile="$PREFIX/$path.gpg"
        set_git "$passfile"

        [[ $inplace -eq 0 && $force -eq 0 && -e $passfile ]] && yesno "An entry 
already exists for $path. Overwrite it?"

        read -r -n $length pass < <(LC_ALL=C tr -dc "$characters" < 
/dev/urandom)
        [[ ${#pass} -eq $length ]] || die "Could not generate password from 
/dev/urandom."
        if [[ $inplace -eq 0 ]]; then
                echo "$pass" | $GPG -e "${GPG_RECIPIENT_ARGS[@]}" -o 
"$passfile" "${GPG_OPTS[@]}" || die "Password encryption aborted."
        else
                local 
passfile_temp="${passfile}.tmp.${RANDOM}.${RANDOM}.${RANDOM}.${RANDOM}.--"
                if { echo "$pass"; $GPG -d "${GPG_OPTS[@]}" "$passfile" | tail 
-n +2; } | $GPG -e "${GPG_RECIPIENT_ARGS[@]}" -o "$passfile_temp" 
"${GPG_OPTS[@]}"; then
                        mv "$passfile_temp" "$passfile"
                else
                        rm -f "$passfile_temp"
                        die "Could not reencrypt new password."
                fi
        fi
        local verb="Add"
        [[ $inplace -eq 1 ]] && verb="Replace"
        git_add_file "$passfile" "$verb generated password for ${path}."

        if [[ $clip -eq 1 ]]; then
                clip "$pass" "$path"
        elif [[ $qrcode -eq 1 ]]; then
                qrcode "$pass" "$path"
        else
                printf "\e[1mThe generated password for \e[4m%s\e[24m 
is:\e[0m\n\e[1m\e[93m%s\e[0m\n" "$path" "$pass"
        fi
}

cmd_delete() {
        local opts recursive="" force=0
        opts="$($GETOPT -o rf -l recursive,force -n "$PROGRAM" -- "$@")"
        local err=$?
        eval set -- "$opts"
        while true; do case $1 in
                -r|--recursive) recursive="-r"; shift ;;
                -f|--force) force=1; shift ;;
                --) shift; break ;;
        esac done
        [[ $# -ne 1 ]] && die "Usage: $PROGRAM $COMMAND [--recursive,-r] 
[--force,-f] pass-name"
        local path="$1"
        check_sneaky_paths "$path"

        local passdir="$PREFIX/${path%/}"
        local passfile="$PREFIX/$path.gpg"
        [[ -f $passfile && -d $passdir && $path == */ || ! -f $passfile ]] && 
passfile="${passdir%/}/"
        [[ -e $passfile ]] || die "Error: $path is not in the password store."
        set_git "$passfile"

        [[ $force -eq 1 ]] || yesno "Are you sure you would like to delete 
$path?"

        rm $recursive -f -v "$passfile"
        set_git "$passfile"
        if [[ -n $INNER_GIT_DIR && ! -e $passfile ]]; then
                git -C "$INNER_GIT_DIR" rm -qr "$passfile"
                set_git "$passfile"
                git_commit "Remove $path from store."
        fi
        rmdir -p "${passfile%/*}" 2>/dev/null
}

cmd_copy_move() {
        local opts move=1 force=0
        [[ $1 == "copy" ]] && move=0
        shift
        opts="$($GETOPT -o f -l force -n "$PROGRAM" -- "$@")"
        local err=$?
        eval set -- "$opts"
        while true; do case $1 in
                -f|--force) force=1; shift ;;
                --) shift; break ;;
        esac done
        [[ $# -ne 2 ]] && die "Usage: $PROGRAM $COMMAND [--force,-f] old-path 
new-path"
        check_sneaky_paths "$@"
        local old_path="$PREFIX/${1%/}"
        local old_dir="$old_path"
        local new_path="$PREFIX/$2"

        if ! [[ -f $old_path.gpg && -d $old_path && $1 == */ || ! -f 
$old_path.gpg ]]; then
                old_dir="${old_path%/*}"
                old_path="${old_path}.gpg"
        fi
        echo "$old_path"
        [[ -e $old_path ]] || die "Error: $1 is not in the password store."

        mkdir -p -v "${new_path%/*}"
        [[ -d $old_path || -d $new_path || $new_path == */ ]] || 
new_path="${new_path}.gpg"

        local interactive="-i"
        [[ ! -t 0 || $force -eq 1 ]] && interactive="-f"

        set_git "$new_path"
        if [[ $move -eq 1 ]]; then
                mv $interactive -v "$old_path" "$new_path" || exit 1
                [[ -e "$new_path" ]] && reencrypt_path "$new_path"

                set_git "$new_path"
                if [[ -n $INNER_GIT_DIR && ! -e $old_path ]]; then
                        git -C "$INNER_GIT_DIR" rm -qr "$old_path" 2>/dev/null
                        set_git "$new_path"
                        git_add_file "$new_path" "Rename ${1} to ${2}."
                fi
                set_git "$old_path"
                if [[ -n $INNER_GIT_DIR && ! -e $old_path ]]; then
                        git -C "$INNER_GIT_DIR" rm -qr "$old_path" 2>/dev/null
                        set_git "$old_path"
                        [[ -n $(git -C "$INNER_GIT_DIR" status --porcelain 
"$old_path") ]] && git_commit "Remove ${1}."
                fi
                rmdir -p "$old_dir" 2>/dev/null
        else
                cp $interactive -r -v "$old_path" "$new_path" || exit 1
                [[ -e "$new_path" ]] && reencrypt_path "$new_path"
                git_add_file "$new_path" "Copy ${1} to ${2}."
        fi
}

cmd_git() {
        set_git "$PREFIX/"
        if [[ $1 == "init" ]]; then
                INNER_GIT_DIR="$PREFIX"
                git -C "$INNER_GIT_DIR" "$@" || exit 1
                git_add_file "$PREFIX" "Add current contents of password store."

                echo '*.gpg diff=gpg' > "$PREFIX/.gitattributes"
                git_add_file .gitattributes "Configure git repository for gpg 
file diff."
                git -C "$INNER_GIT_DIR" config --local diff.gpg.binary true
                git -C "$INNER_GIT_DIR" config --local diff.gpg.textconv "$GPG 
-d ${GPG_OPTS[*]}"
        elif [[ -n $INNER_GIT_DIR ]]; then
                tmpdir nowarn #Defines $SECURE_TMPDIR. We don't warn, because 
at most, this only copies encrypted files.
                export TMPDIR="$SECURE_TMPDIR"
                git -C "$INNER_GIT_DIR" "$@"
        else
                die "Error: the password store is not a git repository. Try 
\"$PROGRAM git init\"."
        fi
}

cmd_extension_or_show() {
        if ! cmd_extension "$@"; then
                COMMAND="show"
                cmd_show "$@"
        fi
}

SYSTEM_EXTENSION_DIR="/usr/lib/password-store/extensions"
cmd_extension() {
        check_sneaky_paths "$1"
        local user_extension system_extension extension
        [[ -n $SYSTEM_EXTENSION_DIR ]] && 
system_extension="$SYSTEM_EXTENSION_DIR/$1.bash"
        [[ $PASSWORD_STORE_ENABLE_EXTENSIONS == true ]] && 
user_extension="$EXTENSIONS/$1.bash"
        if [[ -n $user_extension && -f $user_extension && -x $user_extension 
]]; then
                verify_file "$user_extension"
                extension="$user_extension"
        elif [[ -n $system_extension && -f $system_extension && -x 
$system_extension ]]; then
                extension="$system_extension"
        else
                return 1
        fi
        shift
        source "$extension" "$@"
        return 0
}

#
# END subcommand functions
#

PROGRAM="${0##*/}"
COMMAND="$1"

case "$1" in
        init) shift;                    cmd_init "$@" ;;
        help|--help) shift;             cmd_usage "$@" ;;
        version|--version) shift;       cmd_version "$@" ;;
        show|ls|list) shift;            cmd_show "$@" ;;
        find|search) shift;             cmd_find "$@" ;;
        grep) shift;                    cmd_grep "$@" ;;
        insert|add) shift;              cmd_insert "$@" ;;
        edit) shift;                    cmd_edit "$@" ;;
        generate) shift;                cmd_generate "$@" ;;
        delete|rm|remove) shift;        cmd_delete "$@" ;;
        rename|mv) shift;               cmd_copy_move "move" "$@" ;;
        copy|cp) shift;                 cmd_copy_move "copy" "$@" ;;
        git) shift;                     cmd_git "$@" ;;
        *)                              cmd_extension_or_show "$@" ;;
esac

# power down the OpenPGP card
# g...@unixarea.de
#
gpgconf --reload scdaemon
sleep 2

exit 0
_______________________________________________
Gnupg-users mailing list
Gnupg-users@gnupg.org
https://lists.gnupg.org/mailman/listinfo/gnupg-users

Reply via email to