my dotz
0

Configure Feed

Select the types of activity you want to include in your feed.

add pa

+204
+204
bin/pa
··· 1 + #!/bin/sh 2 + # 3 + # pa - simple password manager based on age 4 + 5 + pw_add() { 6 + name=$1 7 + 8 + if yn "Generate a password?"; then 9 + # Generate a password by reading '/dev/urandom' with the 10 + # 'tr' command to translate the random bytes into a 11 + # configurable character set. 12 + # 13 + # The 'dd' command is then used to read only the desired 14 + # password length. 15 + # 16 + # Regarding usage of '/dev/urandom' instead of '/dev/random'. 17 + # See: https://www.2uo.de/myths-about-urandom 18 + pass=$(LC_ALL=C tr -dc "${PA_PATTERN:-_A-Z-a-z-0-9}" < /dev/urandom | 19 + dd ibs=1 obs=1 count="${PA_LENGTH:-50}" 2>/dev/null) 20 + 21 + else 22 + # 'sread()' is a simple wrapper function around 'read' 23 + # to prevent user input from being printed to the terminal. 24 + sread pass "Enter password" 25 + sread pass2 "Enter password (again)" 26 + 27 + # Disable this check as we dynamically populate the two 28 + # passwords using the 'sread()' function. 29 + # shellcheck disable=2154 30 + [ "$pass" = "$pass2" ] || die "Passwords do not match" 31 + fi 32 + 33 + [ "$pass" ] || die "Failed to generate a password" 34 + 35 + # Mimic the use of an array for storing arguments by... using 36 + # the function's argument list. This is very apt isn't it? 37 + set -- -c 38 + 39 + # Use 'age' to store the password in an encrypted file. 40 + # A heredoc is used here instead of a 'printf' to avoid 41 + # leaking the password through the '/proc' filesystem. 42 + # 43 + # Heredocs are sometimes implemented via temporary files, 44 + # however this is typically done using 'mkstemp()' which 45 + # is more secure than a leak in '/proc'. 46 + pubkey=$(sed -n 's/.*\(age\)/\1/p' ~/.age/key.txt) 47 + age -r "$pubkey" -o "$name.age" <<-EOF && 48 + $pass 49 + EOF 50 + printf '%s\n' "Saved '$name' to the store." 51 + } 52 + 53 + pw_del() { 54 + yn "Delete pass file '$1'?" && { 55 + rm -f "$1.age" 56 + 57 + # Remove empty parent directories of a password 58 + # entry. It's fine if this fails as it means that 59 + # another entry also lives in the same directory. 60 + rmdir -p "${1%/*}" 2>/dev/null || : 61 + } 62 + } 63 + 64 + pw_show() { 65 + age -i ~/.age/key.txt --decrypt "$1.age" 66 + } 67 + 68 + pw_list() { 69 + find . -type f -name \*.age | sed 's/..//;s/\.age$//' 70 + } 71 + 72 + pw_gen() { 73 + if yn "$HOME/.age/key.txt not detected, generate a new one?"; then 74 + mkdir -p ~/.age 75 + age-keygen -o ~/.age/key.txt 76 + fi 77 + } 78 + 79 + yn() { 80 + printf '%s [y/n]: ' "$1" 81 + 82 + # Enable raw input to allow for a single byte to be read from 83 + # stdin without needing to wait for the user to press Return. 84 + stty -icanon 85 + 86 + # Read a single byte from stdin using 'dd'. POSIX 'read' has 87 + # no support for single/'N' byte based input from the user. 88 + answer=$(dd ibs=1 count=1 2>/dev/null) 89 + 90 + # Disable raw input, leaving the terminal how we *should* 91 + # have found it. 92 + stty icanon 93 + 94 + printf '\n' 95 + 96 + # Handle the answer here directly, enabling this function's 97 + # return status to be used in place of checking for '[yY]' 98 + # throughout this program. 99 + glob "$answer" '[yY]' 100 + } 101 + 102 + sread() { 103 + printf '%s: ' "$2" 104 + 105 + # Disable terminal printing while the user inputs their 106 + # password. POSIX 'read' has no '-s' flag which would 107 + # effectively do the same thing. 108 + stty -echo 109 + read -r "$1" 110 + stty echo 111 + 112 + printf '\n' 113 + } 114 + 115 + glob() { 116 + # This is a simple wrapper around a case statement to allow 117 + # for simple string comparisons against globs. 118 + # 119 + # Example: if glob "Hello World" '* World'; then 120 + # 121 + # Disable this warning as it is the intended behavior. 122 + # shellcheck disable=2254 123 + case $1 in $2) return 0; esac; return 1 124 + } 125 + 126 + die() { 127 + printf 'error: %s.\n' "$1" >&2 128 + exit 1 129 + } 130 + 131 + usage() { printf %s "\ 132 + pa 0.1.0 - age-based password manager 133 + => [a]dd [name] - Create a new password, randomly generated 134 + => [d]el [name] - Delete a password entry. 135 + => [l]ist - List all entries. 136 + => [s]how [name] - Show password for an entry. 137 + Password length: export PA_LENGTH=50 138 + Password pattern: export PA_PATTERN=_A-Z-a-z-0-9 139 + Store location: export PA_DIR=~/.local/share/pa 140 + " 141 + exit 0 142 + } 143 + 144 + main() { 145 + : "${PA_DIR:=${XDG_DATA_HOME:=$HOME/.local/share}/pa}" 146 + 147 + command -v age >/dev/null 2>&1 || 148 + die "age not found, install per https://github.com/FiloSottile/age" 149 + 150 + command -v age-keygen >/dev/null 2>&1 || 151 + die "age-keygen not found, install per https://github.com/FiloSottile/age" 152 + 153 + mkdir -p "$PA_DIR" || 154 + die "Couldn't create password directory" 155 + 156 + cd "$PA_DIR" || 157 + die "Can't access password directory" 158 + 159 + [ -f ~/.age/key.txt ] || pw_gen 160 + 161 + glob "$1" '[acds]*' && [ -z "$2" ] && 162 + die "Missing [name] argument" 163 + 164 + glob "$1" '[cds]*' && [ ! -f "$2.age" ] && 165 + die "Pass file '$2' doesn't exist" 166 + 167 + glob "$1" 'a*' && [ -f "$2.age" ] && 168 + die "Pass file '$2' already exists" 169 + 170 + glob "$2" '*/*' && glob "$2" '*../*' && 171 + die "Category went out of bounds" 172 + 173 + glob "$2" '/*' && 174 + die "Category can't start with '/'" 175 + 176 + glob "$2" '*/*' && { mkdir -p "${2%/*}" || 177 + die "Couldn't create category '${2%/*}'"; } 178 + 179 + # Restrict permissions of any new files to 180 + # only the current user. 181 + umask 077 182 + 183 + # Ensure that we leave the terminal in a usable 184 + # state on exit or Ctrl+C. 185 + [ -t 1 ] && trap 'stty echo icanon' INT EXIT 186 + 187 + case $1 in 188 + a*) pw_add "$2" ;; 189 + d*) pw_del "$2" ;; 190 + s*) pw_show "$2" ;; 191 + l*) pw_list ;; 192 + *) usage 193 + esac 194 + } 195 + 196 + # Ensure that debug mode is never enabled to 197 + # prevent the password from leaking. 198 + set +x 199 + 200 + # Ensure that globbing is globally disabled 201 + # to avoid insecurities with word-splitting. 202 + set -f 203 + 204 + [ "$1" ] || usage && main "$@"