Lucas
35002770b8
timeout(1) isn't interruptible, and it fires while you're entering your key's passphrase, giving a subpar UX.
227 lines
4.1 KiB
Bash
227 lines
4.1 KiB
Bash
#!/bin/sh
|
|
# sekrit
|
|
# Written in 2018-2020,2022 by Lucas
|
|
# CC0 1.0 Universal/Public domain - No rights reserved
|
|
#
|
|
# To the extent possible under law, the author(s) have dedicated all
|
|
# copyright and related and neighboring rights to this software to the
|
|
# public domain worldwide. This software is distributed without any
|
|
# warranty. You should have received a copy of the CC0 Public Domain
|
|
# Dedication along with this software. If not, see
|
|
# <http://creativecommons.org/publicdomain/zero/1.0/>.
|
|
|
|
# sekrit is a relatively small shell script for writing and reading
|
|
# encrypted files, aimed to deal mostly with accounts credentials, but
|
|
# general enough to deal with any content. It can populate each key
|
|
# with random data or with fixed data, reading either command-line
|
|
# arguments or stdin.
|
|
#
|
|
# To be used as a password manager, it's recommended to pair it with
|
|
# xclip(1). For example, for username, password and optional second
|
|
# factor:
|
|
#
|
|
# sekrit get account/user | xclip -r -l 1 -sel clip -q
|
|
# sekrit get account/pass | xclip -r -l 1 -sel clip -q
|
|
# sekrit has account/2fa && sekrit get account/2fa |
|
|
# $program_for_totp | xclip -r -l 1 -sel clip -q
|
|
|
|
usage()
|
|
{
|
|
cat - <<. >&2
|
|
Usage:
|
|
${0##*/} add key [value ...]
|
|
${0##*/} cp [-k] key
|
|
${0##*/} gen [-l length] [chars]
|
|
${0##*/} get key
|
|
${0##*/} has key
|
|
${0##*/} ls [keys ...]
|
|
${0##*/} rm [-f] key [key ...]
|
|
|
|
If no value was provided on command line, add reads from stdin.
|
|
.
|
|
exit 1
|
|
}
|
|
|
|
err()
|
|
{
|
|
printf "%s: %s\n" "${0##*/}" "$*" >&2
|
|
exit 1
|
|
}
|
|
|
|
check_key()
|
|
{
|
|
case $1 in
|
|
*/ | /* | ./* | */./* | */. | ../* | */../* | */..)
|
|
err "$1: invalid key"
|
|
;;
|
|
esac
|
|
}
|
|
|
|
make_key_path()
|
|
{
|
|
check_key "$1" && printf "%s/%s.gpg" "$SEKRIT_DIR" "$1"
|
|
}
|
|
|
|
to_number()
|
|
{
|
|
printf "%u" "$*" 2>/dev/null
|
|
}
|
|
|
|
_sekrit_decrypt()
|
|
{
|
|
gpg2 -qd "$1"
|
|
}
|
|
|
|
sekrit_add()
|
|
{
|
|
[ $# -ge 1 ] && [ -n "$1" ] || usage
|
|
key=$1
|
|
shift
|
|
path=$(make_key_path "$key")
|
|
mkdir -p "${path%/*}"
|
|
|
|
[ ! -f "$path" ] || err "key $key already exists"
|
|
|
|
if [ $# -gt 0 ]; then
|
|
# use all additional parameters as a single string
|
|
printf "%s\n" "$*"
|
|
else
|
|
cat -
|
|
fi | gpg2 -qae -r "$SEKRIT_GPG_ID" >"$path"
|
|
}
|
|
|
|
sekrit_cp()
|
|
{
|
|
command -v xclip >/dev/null 2>&1 ||
|
|
err "xclip required for clipboard support"
|
|
|
|
OPTIND=1
|
|
rmlastnl=-rmlastnl
|
|
while getopts k flag; do
|
|
case "$flag" in
|
|
k) rmlastnl= ;;
|
|
*) usage ;;
|
|
esac
|
|
done
|
|
shift $(($OPTIND - 1))
|
|
|
|
[ $# -eq 1 ] || usage
|
|
key=$1
|
|
path=$(make_key_path "$key")
|
|
[ -f "$path" ] || err "no data for key $key"
|
|
_sekrit_decrypt "$path" |
|
|
xclip $rmlastnl -loops 1 -quiet -selection clip 2>/dev/null
|
|
}
|
|
|
|
sekrit_gen()
|
|
{
|
|
OPTIND=1
|
|
len=43
|
|
while getopts l: flag; do
|
|
case "$flag" in
|
|
l) len=$(to_number "$OPTARG") ||
|
|
err "invalid password length"
|
|
;;
|
|
*) usage ;;
|
|
esac
|
|
done
|
|
shift $(($OPTIND - 1))
|
|
|
|
[ $# -le 1 ] || usage
|
|
|
|
chars=+/0-9A-Za-z
|
|
if [ $# -eq 1 ]; then
|
|
[ -n "$1" ] || usage
|
|
chars=$1
|
|
fi
|
|
|
|
tr -cd -- "$chars" </dev/urandom |
|
|
dd bs=1 count="$len" 2>/dev/null &&
|
|
printf "\n"
|
|
}
|
|
|
|
sekrit_get()
|
|
{
|
|
[ $# -eq 1 ] || usage
|
|
key=$1
|
|
path=$(make_key_path "$key")
|
|
[ -f "$path" ] || err "no data for key $key"
|
|
_sekrit_decrypt "$path"
|
|
}
|
|
|
|
sekrit_has()
|
|
{
|
|
[ $# -eq 1 ] || usage
|
|
key=$1
|
|
path=$(make_key_path "$key")
|
|
[ -f "$path" ]
|
|
}
|
|
|
|
ls_key()
|
|
{
|
|
d=$SEKRIT_DIR$1
|
|
find "$d" -type f -name "*.gpg" |
|
|
sort |
|
|
sed -e "s#^$d##" -e 's#\.gpg$##'
|
|
}
|
|
|
|
sekrit_ls()
|
|
{
|
|
if [ $# -eq 0 ]; then
|
|
ls_key /
|
|
else
|
|
for key; do
|
|
printf "%s:\n" "$key"
|
|
ls_key "/$key/" | sed "s/^/ /"
|
|
printf "\n"
|
|
done
|
|
fi
|
|
}
|
|
|
|
sekrit_rm()
|
|
{
|
|
OPTIND=1
|
|
fflag=
|
|
while getopts f flag; do
|
|
case "$flag" in
|
|
f) fflag=-f ;;
|
|
*) usage ;;
|
|
esac
|
|
done
|
|
shift $(($OPTIND - 1))
|
|
|
|
[ $# -ge 1 ] || usage
|
|
|
|
for key; do
|
|
path=$(make_key_path "$key")
|
|
if [ ! -f "$path" ]; then
|
|
printf "%s: no data for key %s\n" "${0##*/}" "$key" >&2
|
|
else
|
|
rm -i $fflag "$path"
|
|
fi
|
|
done
|
|
}
|
|
|
|
set -e
|
|
|
|
[ $# -ge 1 ] || usage
|
|
cmd=$1
|
|
shift
|
|
|
|
umask 077
|
|
: ${SEKRIT_DIR:=~/keep/sekrit}
|
|
mkdir -p "$SEKRIT_DIR"
|
|
|
|
[ -n "$SEKRIT_GPG_ID" ] || err "SEKRIT_GPG_ID is empty"
|
|
|
|
case "$cmd" in
|
|
add) sekrit_add "$@" ;;
|
|
cp) sekrit_cp "$@" ;;
|
|
gen) sekrit_gen "$@" ;;
|
|
get) sekrit_get "$@" ;;
|
|
has) sekrit_has "$@" ;;
|
|
ls) sekrit_ls "$@" ;;
|
|
rm) sekrit_rm "$@" ;;
|
|
*) usage ;;
|
|
esac
|