my dotz
0

Configure Feed

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

1#!/bin/sh 2# 3# pa - simple age-based password manager 4 5pw_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 52pw_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 81pw_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 92pw_show() { 93 age -i ~/.age/key.txt --decrypt "$1.age" 2>/dev/null || 94 die "Could not decrypt $1.age" 95} 96 97pw_list() { 98 find . -type f -name \*.age | sed 's/..//;s/\.age$//' 99} 100 101pw_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 108yn() { 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 131sread() { 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 144glob() { 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 155die() { 156 printf 'error: %s.\n' "$1" >&2 157 exit 1 158} 159 160usage() { printf %s "\ 161pa 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. 167Password length: export PA_LENGTH=50 168Password pattern: export PA_PATTERN=_A-Z-a-z-0-9 169Store location: export PA_DIR=~/.local/share/pa 170" 171exit 0 172} 173 174main() { 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. 230set +x 231 232# Ensure that globbing is globally disabled 233# to avoid insecurities with word-splitting. 234set -f 235 236[ "$1" ] || usage && main "$@"