#!/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() { gpg -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 | gpg -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