Last active
December 30, 2016 10:08
-
-
Save yrps/cf2a3155fb147d25e65592c4d067a0a7 to your computer and use it in GitHub Desktop.
passuniq - look for duplicate or blank passwords in your password store.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/bin/bash | |
script="${0##*/}" | |
if [[ -z "${BASH_VERSINFO[0]}" || "${BASH_VERSINFO[0]}" -lt 4 ]]; then | |
2>&1 printf "%s requires bash>=4.\n" "$script" | |
exit 128 | |
fi | |
min=20 | |
declare -A retcode color | |
retcode=( [blank]=1 [dupe]=2 [short]=4 [error]=8 [invoc]=254 [none]=255 ) | |
color=( [error]=1 [ok]=2 [dupe]=3 [short]=5 [blank]=7 ) | |
usage() { | |
cat <<USAGE | |
NAME | |
$script - identify duplicate, blank, or weak passwords in your password store. | |
SYNOPSIS | |
$script [options] | |
$script [options] subdir | |
DESCRIPTION | |
$script uses gpg(1), or gpg2(1) if available, to check if all files in the | |
password store have unique initial lines as their cleartext. | |
If available, gpg-agent(1) is used to faciliate batch operation. | |
OPTIONS | |
-q, --quiet | |
This option suppresses password counting. | |
Use twice to suppress reporting blanks and duplicates. | |
Under no circumstances are passwords output. | |
-m LENGTH, --min LENGTH | |
Consider LENGTH characters to be the minimum length for a good password. | |
Default is $min. Set to 0 to skip checking password length. | |
Passwords which consist only of digits will be ignored. | |
-c COUNT, --count COUNT | |
Stop processing after COUNT entries. Default is to do them all. | |
-h, --help | |
Display this help text and exit. | |
If a subdir is passed, $script will only search the named subdirectory of the | |
password store directory. | |
ENVIRONMENT VARIABLES | |
PASSWORD_STORE_DIR | |
If present, overrides the default password store directory. | |
RETURN CODES | |
${retcode[none]} if no GPG-encrypted files were found in the password store directory. | |
${retcode[invoc]} if there were invocation errors. | |
0 if there are no duplicates, blank passwords, or decryption errors. | |
Otherwise, a bitwise disjunction denoting problems encountered: | |
${retcode[blank]} for blank passwords. | |
${retcode[dupe]} for duplicate passwords. | |
${retcode[short]} for passwords below minimum length. | |
${retcode[error]} for decryption errors. | |
ENVIRONMENT | |
PASSWORD_STORE_DIR overrides the location to be searched. | |
The default is ~/.password-store. | |
SEE ALSO | |
pass(1), gpg(1), gpg2(1), gpg-agent(1) | |
USAGE | |
} | |
parsed="$(getopt -o qhm:c: -l quiet,help,min:,count: -n "$script" -- "$@")" || | |
exit 254 | |
eval set -- "$parsed" | |
unset parsed | |
quiet=0 | |
while true; do | |
case $1 in | |
-q|--quiet) | |
((quiet++)) | |
shift | |
;; | |
-h|--help) | |
usage | |
exit 0 | |
;; | |
-m|--min) | |
min="$2" | |
if [[ ! "$min" =~ ^[0-9]+$ ]]; then | |
printf "min must be a number of characters, not %s\n" "$min" | |
exit 254 | |
fi | |
shift 2 | |
;; | |
-c|--count) | |
cnt="$2" | |
if [[ ! "$cnt" =~ ^[0-9]+$ ]]; then | |
printf "count must be a number of entries, not %s\n" "$cnt" | |
exit 254 | |
fi | |
shift 2 | |
;; | |
--) | |
shift | |
break | |
;; | |
esac | |
done | |
# Expand double *; do not expand unmatched * | |
shopt -s globstar nullglob | |
set -o pipefail | |
color_print() { | |
[ -t 1 ] && printf '%s' "$(tput setaf "$1")" | |
printf '%s' "$2" | |
[ -t 1 ] && printf '%s' "$(tput sgr0)" | |
} | |
symbol() { | |
[ $quiet -gt 0 ] && return | |
local c="${color[$1]}" | |
case $1 in | |
error) | |
color_print "$c" $'\u2717' ;; | |
ok) | |
color_print "$c" $'\u2713' ;; | |
dupe) | |
color_print "$c" $'\u26A0' ;; | |
short) | |
color_print "$c" $'\u26A0' ;; | |
blank) | |
color_print "$c" $'\u237D' ;; | |
esac | |
} | |
GPG_OPTS=( "--quiet" "--yes" "--batch" "--use-agent" ) | |
GPG="gpg" | |
export GPG_TTY="${GPG_TTY:-$(tty 2>/dev/null)}" | |
which gpg2 &>/dev/null && GPG="gpg2" | |
prefix="${PASSWORD_STORE_DIR:-$HOME/.password-store}" | |
[ -n "$1" ] && prefix+="/$1" | |
declare -A lists | |
passcount=0 blanklist='' shortlist='' errorlist='' _exit=0 | |
for passfile in "$prefix"/**/*.gpg; do | |
[[ -n "$cnt" && "$passcount" -ge "$cnt" ]] && break | |
((passcount++)) | |
printed= | |
val="${passfile#$prefix/}" | |
val="${val%.gpg}" | |
read -r key <<< "$(2>/dev/null $GPG --decrypt "${GPG_OPTS[@]}" "$passfile")" | |
_status="$?" | |
if [ "$_status" -gt 0 ]; then | |
errorlist+="$val\n" | |
_exit=$((_exit | retcode[error])) | |
symbol error | |
continue | |
elif [ -z "$key" ]; then | |
blanklist+="$val\n" | |
_exit=$((_exit | retcode[blank])) | |
symbol blank | |
continue | |
fi | |
if [[ "$min" -gt 0 && "${#key}" -lt "$min" && ! "$key" =~ ^[0-9]+$ ]]; then | |
shortlist+="$(printf '%2d %s' "${#key}" "$val")" | |
shortlist+=$'\n' | |
_exit=$((_exit | retcode[short])) | |
symbol short | |
printed=1 | |
fi | |
if [ -n "${lists["$key"]}" ]; then | |
_exit=$((_exit | retcode[dupe])) | |
[ -z "$printed" ] && symbol dupe | |
printed=1 | |
fi | |
lists["$key"]+="$val\n" | |
[ -z "$printed" ] && symbol ok | |
done | |
unset printed key val | |
[ "$quiet" -eq 0 ] && printf "\nPasswords processed: %d\n" "$passcount" | |
printf "\n" | |
if [ $_exit -eq 0 ] && [ -z "${!lists[*]}" ]; then | |
1>&2 printf "No encrypted files were found in %s.\n\n" "$prefix" | |
exit 255 | |
fi | |
if [ $quiet -lt 2 ]; then | |
count_and_print() { | |
local list="$1" col="$2" plural="$3" singular="$4" files | |
[ -z "$list" ] && return | |
files="$(printf "%b" "$list" | wc -l)" | |
if [ "$files" -eq 1 ]; then | |
[[ -z "$singular" ]] && return | |
message="$singular" | |
else | |
message="$plural" | |
fi | |
color_print "$col" "$(printf "%d %s:" "$files" "$message")" | |
printf "\n%b\n" "$list" | |
} | |
count_and_print "$blanklist" "${color[blank]}" \ | |
"files have no password" "file has no password" | |
count_and_print "$shortlist" "${color[short]}" \ | |
"files have password length<$min" "file has password length<$min" | |
for key in "${!lists[@]}"; do | |
count_and_print "${lists["$key"]}" "${color[dupe]}" \ | |
"files have the same password" | |
done | |
count_and_print "$errorlist" "${color[error]}" \ | |
"files had decryption errors" "file had decryption errors" | |
fi | |
if [[ "$_exit" -eq 0 && $quiet -lt 2 ]]; then | |
color_print "${color[ok]}" "No problems detected." | |
printf "\n\n" | |
fi | |
exit $_exit |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment