my dotz
2
fork

Configure Feed

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

pa -> link

+1 -236
+1 -236
bin/pa
··· 1 - #!/bin/sh 2 - # 3 - # pa - simple age-based password manager 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 - age -r "$pubkey" -o "$name.age" <<-EOF && 47 - $pass 48 - EOF 49 - printf '%s\n' "Saved '$name' to the store." 50 - } 51 - 52 - pw_edit() { 53 - name=$1 54 - 55 - [ -f "$name.age" ] || die "Failed to access $name" 56 - 57 - # we use /dev/shm because it's an in-memory 58 - # space that we can use to store private data, 59 - # and securely wipe it without worrying about 60 - # residual badness 61 - [ -d /dev/shm ] || die "Failed to access /dev/shm" 62 - 63 - # get base dirname in case we're dealing with 64 - # a nested item (foo/bar) 65 - tmpfile="/dev/shm/pa/$name.txt" 66 - tmpdir="$(dirname $tmpfile)" 67 - mkdir -p "$tmpdir" 68 - trap 'rm -rf /dev/shm/pa' EXIT 69 - 70 - age -i ~/.age/key.txt --decrypt "$name.age" 2>/dev/null > "$tmpfile" || 71 - die "Could not decrypt $name.age" 72 - 73 - "${EDITOR:-vi}" "$tmpfile" 74 - 75 - [ -f "$tmpfile" ] || die "New password not saved" 76 - 77 - rm "$name.age" 78 - age -r "$pubkey" -o "$name.age" "$tmpfile" 79 - } 80 - 81 - pw_del() { 82 - yn "Delete pass file '$1'?" && { 83 - rm -f "$1.age" 84 - 85 - # Remove empty parent directories of a password 86 - # entry. It's fine if this fails as it means that 87 - # another entry also lives in the same directory. 88 - rmdir -p "${1%/*}" 2>/dev/null || : 89 - } 90 - } 91 - 92 - pw_show() { 93 - age -i ~/.age/key.txt --decrypt "$1.age" 2>/dev/null || 94 - die "Could not decrypt $1.age" 95 - } 96 - 97 - pw_list() { 98 - find . -type f -name \*.age | sed 's/..//;s/\.age$//' 99 - } 100 - 101 - pw_gen() { 102 - if yn "$HOME/.age/key.txt not detected, generate a new one?"; then 103 - mkdir -p ~/.age 104 - age-keygen -o ~/.age/key.txt 105 - fi 106 - } 107 - 108 - yn() { 109 - printf '%s [y/n]: ' "$1" 110 - 111 - # Enable raw input to allow for a single byte to be read from 112 - # stdin without needing to wait for the user to press Return. 113 - stty -icanon 114 - 115 - # Read a single byte from stdin using 'dd'. POSIX 'read' has 116 - # no support for single/'N' byte based input from the user. 117 - answer=$(dd ibs=1 count=1 2>/dev/null) 118 - 119 - # Disable raw input, leaving the terminal how we *should* 120 - # have found it. 121 - stty icanon 122 - 123 - printf '\n' 124 - 125 - # Handle the answer here directly, enabling this function's 126 - # return status to be used in place of checking for '[yY]' 127 - # throughout this program. 128 - glob "$answer" '[yY]' 129 - } 130 - 131 - sread() { 132 - printf '%s: ' "$2" 133 - 134 - # Disable terminal printing while the user inputs their 135 - # password. POSIX 'read' has no '-s' flag which would 136 - # effectively do the same thing. 137 - stty -echo 138 - read -r "$1" 139 - stty echo 140 - 141 - printf '\n' 142 - } 143 - 144 - glob() { 145 - # This is a simple wrapper around a case statement to allow 146 - # for simple string comparisons against globs. 147 - # 148 - # Example: if glob "Hello World" '* World'; then 149 - # 150 - # Disable this warning as it is the intended behavior. 151 - # shellcheck disable=2254 152 - case $1 in $2) return 0; esac; return 1 153 - } 154 - 155 - die() { 156 - printf 'error: %s.\n' "$1" >&2 157 - exit 1 158 - } 159 - 160 - usage() { printf %s "\ 161 - pa 0.1.0 - age-based password manager 162 - => [a]dd [name] - Create a new password, randomly generated 163 - => [d]el [name] - Delete a password entry. 164 - => [e]dit [name] - Edit a password entry with $EDITOR. 165 - => [l]ist - List all entries. 166 - => [s]how [name] - Show password for an entry. 167 - Password length: export PA_LENGTH=50 168 - Password pattern: export PA_PATTERN=_A-Z-a-z-0-9 169 - Store location: export PA_DIR=~/.local/share/pa 170 - " 171 - exit 0 172 - } 173 - 174 - main() { 175 - : "${PA_DIR:=${XDG_DATA_HOME:=$HOME/.local/share}/pa}" 176 - 177 - command -v age >/dev/null 2>&1 || 178 - die "age not found, install per https://github.com/FiloSottile/age" 179 - 180 - command -v age-keygen >/dev/null 2>&1 || 181 - die "age-keygen not found, install per https://github.com/FiloSottile/age" 182 - 183 - mkdir -p "$PA_DIR" || 184 - die "Couldn't create password directory" 185 - 186 - cd "$PA_DIR" || 187 - die "Can't access password directory" 188 - 189 - glob "$1" '[acdes]*' && [ -z "$2" ] && 190 - die "Missing [name] argument" 191 - 192 - glob "$1" '[cds]*' && [ ! -f "$2.age" ] && 193 - die "Pass file '$2' doesn't exist" 194 - 195 - glob "$1" 'a*' && [ -f "$2.age" ] && 196 - die "Pass file '$2' already exists" 197 - 198 - glob "$2" '*/*' && glob "$2" '*../*' && 199 - die "Category went out of bounds" 200 - 201 - glob "$2" '/*' && 202 - die "Category can't start with '/'" 203 - 204 - glob "$2" '*/*' && { mkdir -p "${2%/*}" || 205 - die "Couldn't create category '${2%/*}'"; } 206 - 207 - # Restrict permissions of any new files to 208 - # only the current user. 209 - umask 077 210 - 211 - [ -f ~/.age/key.txt ] || pw_gen 212 - pubkey=$(sed -n 's/.*\(age\)/\1/p' ~/.age/key.txt) 213 - 214 - # Ensure that we leave the terminal in a usable 215 - # state on exit or Ctrl+C. 216 - [ -t 1 ] && trap 'stty echo icanon' INT EXIT 217 - 218 - case $1 in 219 - a*) pw_add "$2" ;; 220 - d*) pw_del "$2" ;; 221 - e*) pw_edit "$2" ;; 222 - s*) pw_show "$2" ;; 223 - l*) pw_list ;; 224 - *) usage 225 - esac 226 - } 227 - 228 - # Ensure that debug mode is never enabled to 229 - # prevent the password from leaking. 230 - set +x 231 - 232 - # Ensure that globbing is globally disabled 233 - # to avoid insecurities with word-splitting. 234 - set -f 235 - 236 - [ "$1" ] || usage && main "$@" 1 + /home/j3s/code/pa/pa