npm

Nihal's Password Manager (WIP)
git clone git://git.nihaljere.xyz/npm
Log | Files | Refs | LICENSE

commit a8d0411a61ac3a8045de370ed346edf923bc85fb
parent 55fc794cac34496fb5f1ba9d0824683ce976a23d
Author: Nihal Jere <nihal@nihaljere.xyz>
Date:   Tue,  7 Sep 2021 13:02:47 -0500

rename npwm to npm

Diffstat:
Anpm | 246+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Dnpwm | 232-------------------------------------------------------------------------------
2 files changed, 246 insertions(+), 232 deletions(-)

diff --git a/npm b/npm @@ -0,0 +1,246 @@ +#!/bin/sh +# +# pash - simple password manager. + +core="/home/nihal/projects/npm/npm-core" +npmc="/home/nihal/projects/npm/npmc" + +pw_add() { + name=$1 + + if yn "Generate a password?"; then + # Generate a password by reading '/dev/urandom' with the + # 'tr' command to translate the random bytes into a + # configurable character set. + # + # The 'dd' command is then used to read only the desired + # password length. + # + # Regarding usage of '/dev/urandom' instead of '/dev/random'. + # See: https://www.2uo.de/myths-about-urandom + pass=$(LC_ALL=C tr -dc "${PASH_PATTERN:-_A-Z-a-z-0-9}" < /dev/urandom | + dd ibs=1 obs=1 count="${PASH_LENGTH:-50}" 2>/dev/null) + + else + # 'sread()' is a simple wrapper function around 'read' + # to prevent user input from being printed to the terminal. + sread pass "Enter password" + sread pass2 "Enter password (again)" + + # Disable this check as we dynamically populate the two + # passwords using the 'sread()' function. + # shellcheck disable=2154 + [ "$pass" = "$pass2" ] || die "Passwords do not match" + fi + + [ "$pass" ] || die "Failed to generate a password" + + # Mimic the use of an array for storing arguments by... using + # the function's argument list. This is very apt isn't it? + if [ "$PASH_KEYID" ]; then + set -- --trust-model always -aer "$PASH_KEYID" + else + set -- -c + fi + + # Use 'gpg' to store the password in an encrypted file. + # A heredoc is used here instead of a 'printf' to avoid + # leaking the password through the '/proc' filesystem. + # + # Heredocs are sometimes implemented via temporary files, + # however this is typically done using 'mkstemp()' which + # is more secure than '/proc'. + printf "got here: %s\n" "$name" + "$core" -e > "$name.npm" <<-EOF && \ + printf '%s\n' "Saved '$name' to the store." + $(echo "" | bemenu -x -p "Passphrase:") + $pass + EOF + printf "got here2\n" +} + +pw_del() { + yn "Delete pass file '$1'?" && { + rm -f "$1.npm" + rmdir -p "${1%/*}" 2>/dev/null + } +} + +pw_show() { + "$npmc" "$1.npm" +} + +pw_copy() { + # Disable warning against word-splitting as it is safe + # and intentional (globbing is disabled). + # shellcheck disable=2086 + : "${PASH_CLIP:=xclip -sel c}" + + # Wait in the background for the password timeout and + # clear the clipboard when the timer runs out. + # + # If the 'sleep' fails, kill the script. This is the + # simplest method of aborting from a subshell. + [ "$PASH_TIMEOUT" != off ] && { + printf 'Clearing clipboard in "%s" seconds.\n' "${PASH_TIMEOUT:=15}" + + sleep "$PASH_TIMEOUT" || kill 0 + $PASH_CLIP </dev/null + } & + + pw_show "$1" | $PASH_CLIP +} + +pw_list() { + find . -type f -name \*.npm | sed 's/..//;s/\.npm$//' +} + +pw_tree() { + command -v tree >/dev/null 2>&1 || + die "'tree' command not found" + + tree --noreport | sed 's/\.npm$//' +} + +yn() { + printf '%s [y/n]: ' "$1" + + # Enable raw input to allow for a single byte to be read from + # stdin without needing to wait for the user to press Return. + stty -icanon + + # Read a single byte from stdin using 'dd'. POSIX 'read' has + # no support for single/'N' byte based input from the user. + answer=$(dd ibs=1 count=1 2>/dev/null) + + # Disable raw input, leaving the terminal how we *should* + # have found it. + stty icanon + + printf '\n' + + # Handle the answer here directly, enabling this function's + # return status to be used in place of checking for '[yY]' + # throughout this program. + glob "$answer" '[yY]' +} + +sread() { + printf '%s: ' "$2" + + # Disable terminal printing while the user inputs their + # password. POSIX 'read' has no '-s' flag which would + # effectively do the same thing. + stty -echo + read -r "$1" + stty echo + + printf '\n' +} + +glob() { + # This is a simple wrapper around a case statement to allow + # for simple string comparisons against globs. + # + # Example: if glob "Hello World" '* World'; then + # + # Disable this warning as it is the intended behavior. + # shellcheck disable=2254 + case $1 in $2) return 0; esac; return 1 +} + +die() { + printf 'error: %s.\n' "$1" >&2 + exit 1 +} + +usage() { printf %s "\ +pash 2.3.0 - simple password manager. + +=> [a]dd [name] - Create a new password entry. +=> [c]opy [name] - Copy entry to the clipboard. +=> [d]el [name] - Delete a password entry. +=> [l]ist - List all entries. +=> [s]how [name] - Show password for an entry. +=> [t]ree - List all entries in a tree. + +Using a key pair: export PASH_KEYID=XXXXXXXX +Password length: export PASH_LENGTH=50 +Password pattern: export PASH_PATTERN=_A-Z-a-z-0-9 +Store location: export PASH_DIR=~/.local/share/pash +Clipboard tool: export PASH_CLIP='xclip -sel c' +Clipboard timeout: export PASH_TIMEOUT=15 ('off' to disable) +" +exit 1 +} + +main() { + : "${NPM_DIR:=${XDG_DATA_HOME:=$HOME/.local/share}/npm}" + + [ "$1" = '-?' ] || [ -z "$1" ] && + usage + + # Look for both 'gpg' and 'gpg2', + # preferring 'gpg2' if it is available. + command -v gpg >/dev/null 2>&1 && gpg=gpg + command -v gpg2 >/dev/null 2>&1 && gpg=gpg2 + + [ "$gpg" ] || + die "GPG not found" + + mkdir -p "$NPM_DIR" || + die "Couldn't create password directory" + + cd "$NPM_DIR" || + die "Can't access password directory" + + glob "$1" '[acds]*' && [ -z "$2" ] && + die "Missing [name] argument" + + glob "$1" '[cds]*' && [ ! -f "$2.npm" ] && + die "Pass file '$2' doesn't exist" + +glob "$1" 'a*' && [ -f "$2.npm" ] && + die "Pass file '$2' already exists" + + glob "$2" '*/*' && glob "$2" '*../*' && + die "Category went out of bounds" + + glob "$2" '/*' && + die "Category can't start with '/'" + + glob "$2" '*/*' && { mkdir -p "${2%/*}" || + die "Couldn't create category '${2%/*}'"; } + + # Set 'GPG_TTY' to the current 'TTY' if it + # is unset. Fixes a somewhat rare `gpg` issue. + export GPG_TTY=${GPG_TTY:-$(tty)} + + # Restrict permissions of any new files to + # only the current user. + umask 077 + + # Ensure that we leave the terminal in a usable + # state on exit or Ctrl+C. + trap 'stty echo icanon' INT EXIT + + case $1 in + a*) pw_add "$2" ;; + c*) pw_copy "$2" ;; + d*) pw_del "$2" ;; + s*) pw_show "$2" ;; + l*) pw_list ;; + t*) pw_tree ;; + *) usage + esac +} + +# Ensure that debug mode is never enabled to +# prevent the password from leaking. +set +x + +# Ensure that globbing is globally disabled +# to avoid insecurities with word-splitting. +set -f + +main "$@" diff --git a/npwm b/npwm @@ -1,232 +0,0 @@ -#!/bin/sh -# -# pash - simple password manager. - -pw_add() { - name=$1 - - if yn "Generate a password?"; then - # Generate a password by reading '/dev/urandom' with the - # 'tr' command to translate the random bytes into a - # configurable character set. - # - # The 'dd' command is then used to read only the desired - # password length. - # - # Regarding usage of '/dev/urandom' instead of '/dev/random'. - # See: https://www.2uo.de/myths-about-urandom - pass=$(LC_ALL=C tr -dc "${PASH_PATTERN:-_A-Z-a-z-0-9}" < /dev/urandom | - dd ibs=1 obs=1 count="${PASH_LENGTH:-50}" 2>/dev/null) - - else - # 'sread()' is a simple wrapper function around 'read' - # to prevent user input from being printed to the terminal. - sread pass "Enter password" - sread pass2 "Enter password (again)" - - # Disable this check as we dynamically populate the two - # passwords using the 'sread()' function. - # shellcheck disable=2154 - [ "$pass" = "$pass2" ] || die "Passwords do not match" - fi - - [ "$pass" ] || die "Failed to generate a password" - - # Use 'gpg' to store the password in an encrypted file. - # A heredoc is used here instead of a 'printf' to avoid - # leaking the password through the '/proc' filesystem. - # - # Heredocs are sometimes implemented via temporary files, - # however this is typically done using 'mkstemp()' which - # is more secure than a leak in '/proc'. - "$gpg" -o "$name.gpg" <<-EOF && - $pass - EOF - printf '%s\n' "Saved '$name' to the store." -} - -pw_del() { - yn "Delete pass file '$1'?" && { - rm -f "$1.gpg" - - # Remove empty parent directories of a password - # entry. It's fine if this fails as it means that - # another entry also lives in the same directory. - rmdir -p "${1%/*}" 2>/dev/null || : - } -} - -pw_show() { - "$gpg" -dq "$1.gpg" -} - -pw_copy() { - # Disable warning against word-splitting as it is safe - # and intentional (globbing is disabled). - # shellcheck disable=2086 - : "${PASH_CLIP:=xclip -sel c}" - - # Wait in the background for the password timeout and - # clear the clipboard when the timer runs out. - # - # If the 'sleep' fails, kill the script. This is the - # simplest method of aborting from a subshell. - [ "$PASH_TIMEOUT" != off ] && { - printf 'Clearing clipboard in "%s" seconds.\n' "${PASH_TIMEOUT:=15}" - - sleep "$PASH_TIMEOUT" || kill 0 - $PASH_CLIP </dev/null - } & - - pw_show "$1" | $PASH_CLIP -} - -pw_list() { - find . -type f -name \*.gpg | sed 's/..//;s/\.gpg$//' -} - -pw_tree() { - command -v tree >/dev/null 2>&1 || - die "'tree' command not found" - - tree --noreport | sed 's/\.gpg$//' -} - -yn() { - printf '%s [y/n]: ' "$1" - - # Enable raw input to allow for a single byte to be read from - # stdin without needing to wait for the user to press Return. - stty -icanon - - # Read a single byte from stdin using 'dd'. POSIX 'read' has - # no support for single/'N' byte based input from the user. - answer=$(dd ibs=1 count=1 2>/dev/null) - - # Disable raw input, leaving the terminal how we *should* - # have found it. - stty icanon - - printf '\n' - - # Handle the answer here directly, enabling this function's - # return status to be used in place of checking for '[yY]' - # throughout this program. - glob "$answer" '[yY]' -} - -sread() { - printf '%s: ' "$2" - - # Disable terminal printing while the user inputs their - # password. POSIX 'read' has no '-s' flag which would - # effectively do the same thing. - stty -echo - read -r "$1" - stty echo - - printf '\n' -} - -glob() { - # This is a simple wrapper around a case statement to allow - # for simple string comparisons against globs. - # - # Example: if glob "Hello World" '* World'; then - # - # Disable this warning as it is the intended behavior. - # shellcheck disable=2254 - case $1 in $2) return 0; esac; return 1 -} - -die() { - printf 'error: %s.\n' "$1" >&2 - exit 1 -} - -usage() { printf %s "\ -pash 2.3.0 - simple password manager. - -=> [a]dd [name] - Create a new password entry. -=> [c]opy [name] - Copy entry to the clipboard. -=> [d]el [name] - Delete a password entry. -=> [l]ist - List all entries. -=> [s]how [name] - Show password for an entry. -=> [t]ree - List all entries in a tree. - -Password length: export PASH_LENGTH=50 -Password pattern: export PASH_PATTERN=_A-Z-a-z-0-9 -Store location: export PASH_DIR=~/.local/share/pash -Clipboard tool: export PASH_CLIP='xclip -sel c' -Clipboard timeout: export PASH_TIMEOUT=15 ('off' to disable) -" -exit 0 -} - -main() { - : "${PASH_DIR:=${XDG_DATA_HOME:=$HOME/.local/share}/pash}" - - # Look for both 'gpg' and 'gpg2', - # preferring 'gpg2' if it is available. - command -v gpg >/dev/null 2>&1 && gpg=gpg - command -v gpg2 >/dev/null 2>&1 && gpg=gpg2 - - [ "$gpg" ] || - die "GPG not found" - - mkdir -p "$PASH_DIR" || - die "Couldn't create password directory" - - cd "$PASH_DIR" || - die "Can't access password directory" - - glob "$1" '[acds]*' && [ -z "$2" ] && - die "Missing [name] argument" - - glob "$1" '[cds]*' && [ ! -f "$2.gpg" ] && - die "Pass file '$2' doesn't exist" - - glob "$1" 'a*' && [ -f "$2.gpg" ] && - die "Pass file '$2' already exists" - - glob "$2" '*/*' && glob "$2" '*../*' && - die "Category went out of bounds" - - glob "$2" '/*' && - die "Category can't start with '/'" - - glob "$2" '*/*' && { mkdir -p "${2%/*}" || - die "Couldn't create category '${2%/*}'"; } - - # Set 'GPG_TTY' to the current 'TTY' if it - # is unset. Fixes a somewhat rare `gpg` issue. - export GPG_TTY=${GPG_TTY:-$(tty)} - - # Restrict permissions of any new files to - # only the current user. - umask 077 - - # Ensure that we leave the terminal in a usable - # state on exit or Ctrl+C. - [ -t 1 ] && trap 'stty echo icanon' INT EXIT - - case $1 in - a*) pw_add "$2" ;; - c*) pw_copy "$2" ;; - d*) pw_del "$2" ;; - s*) pw_show "$2" ;; - l*) pw_list ;; - t*) pw_tree ;; - *) usage - esac -} - -# Ensure that debug mode is never enabled to -# prevent the password from leaking. -set +x - -# Ensure that globbing is globally disabled -# to avoid insecurities with word-splitting. -set -f - -[ "$1" ] || usage && main "$@"