#!/bin/sh # Copyright 2021 Mitchell Riedstra # # Permission to use, copy, modify, and/or distribute this software for any purpose # with or without fee is hereby granted, provided that the above copyright notice # and this permission notice appear in all copies. # # THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH # REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND # FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, # INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS # OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER # TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF # THIS SOFTWARE. # # Example storage plugin that wraps 'age' into a password manager. # https://github.com/FiloSottile/age set -e UMASK="${PASSWORD_STORE_UMASK:-077}" umask "$UMASK" DPW_AGE_DIR="${DPW_AGE_DIR:-$HOME/.dpw-age}" DPW_AGE_KEY="${DPW_AGE_KEY:-$HOME/.dpw-age-key}" USE_GIT=0 [ -e "${DPW_AGE_DIR}/.git" ] && USE_GIT=1 # Helper functions _git_commit() { [ $USE_GIT -eq 0 ] && return cd "${DPW_AGE_DIR}" git add --all git commit -am "DPW Auto-commit: $1" } _set_age_recipients() { if [ -n "$DPW_AGE_RECIPIENTS" ] ; then DPW_AGE_RECIPIENTS="$(echo "$DPW_AGE_RECIPIENTS" | tr ' ' '\n' \ | sed -e's/^/ -r/' | tr '\n' ' ')" return fi _pth="$1"; shift _pth="${DPW_AGE_DIR}/$_pth" id_file="$(dirname "${_pth}")/.recipients" while true ; do # Break if id_file is above our password store directory case $id_file in ${DPW_AGE_DIR}*) ;; *) break ;; esac if [ -e "$id_file" ] ; then keys="" while read -r key ; do if [ -z "$key" ] ; then continue ; fi keys="$keys -r $key" done < "$id_file" export DPW_AGE_RECIPIENTS="$keys" return fi # Pop this up a directory level for the next time 'round id_file="$(dirname "$id_file")" id_file="$(dirname "$id_file")/.recipients" done echo "No '.recipients' files found, is '$DPW_AGE_DIR' initialized?" exit 1 } # Interface show() { pth="$1"; shift #shellcheck disable=SC2086 exec age -i "${DPW_AGE_KEY}" -d < "${DPW_AGE_DIR}/${pth}.age" } insert() { pth="$1"; shift _set_age_recipients "$pth" mkdir -p "$DPW_AGE_DIR/$(dirname "$pth")" #shellcheck disable=SC2086 age -e ${DPW_AGE_RECIPIENTS} \ > "${DPW_AGE_DIR}/${pth}.age" _git_commit "Insert: $pth" } list() { find "$DPW_AGE_DIR/$1" -type f -iname "*.age" \ | while read -r line ; do line="${line##$DPW_AGE_DIR}" line="${line#/}" line="${line#/}" line="${line%.age}" echo "$line" done } remove() { cd "$DPW_AGE_DIR" recursive= force= while [ $# -gt 0 ] ; do case $1 in -r) recursive="-r" ; shift ;; -f) force="-f" ; shift ;; -rf|-fr) recursive="-r" ; force="-f" ; shift ;; *) break ;; esac ; done files= for fn in "$@" ; do if [ -e "${fn}.age" ] ; then files="${fn}.age " else files="$fn " fi done #shellcheck disable=SC2086 rm $recursive $force $files _git_commit "Remove: $*" } _init_help() { cat <... Git is the default. Gen key will generate a passphrase protected private key for you and place it in \$DPW_AGE_KEY ($DPW_AGE_KEY) and the public side will be '.pub' No pass disables the default for a passphrase protected key file The key will automatically be added to the recipients list. Multiple recipients may be specified See: https://github.com/FiloSottile/age for more information on 'age' itself. EOF exit 1 } _init() { USE_GIT=1 if [ -d "${DPW_AGE_DIR}" ] ; then echo "Cannot init new password store, one exists" exit 1 fi recipients= genkey=1 passphrase=1 while [ $# -gt 0 ] ; do case $1 in --no-git) USE_GIT=0 ; shift ;; --gen-key) genkey=1; shift ;; --pass) passphrase=1; shift ;; --no-pass) passphrase=0; shift ;; -r|--recipient) recipients="$recipients:$2"; shift ; shift ;; *) _init_help ;; esac ; done [ $genkey -eq 1 ] && [ -e "${DPW_AGE_KEY}" ] \ && echo "Cannot continue, \"${DPW_AGE_KEY}\" already exists" && exit echo "genkey: $genkey passphrase: $passphrase" if [ $genkey -eq 1 ] && [ $passphrase -eq 1 ] ; then touch "${DPW_AGE_KEY}.pub" # Let the user's UMASK bet set umask 077 # but override it for the key age-keygen 2>"${DPW_AGE_KEY}.pub" | age -p > "${DPW_AGE_KEY}" 2>&2 sed -i.bak -e's/^Public key: //g' "${DPW_AGE_KEY}.pub" rm "${DPW_AGE_KEY}.pub.bak" umask "$UMASK" elif [ $genkey -eq 1 ] ; then touch "${DPW_AGE_KEY}.pub" # Let the user's UMASK bet set umask 077 # but override it for the key age-keygen >"${DPW_AGE_KEY}" sed -ne's/^# public key: //gp' < "${DPW_AGE_KEY}" > "${DPW_AGE_KEY}.pub" umask "$UMASK" fi mkdir -p "${DPW_AGE_DIR}" cd "${DPW_AGE_DIR}" if [ $USE_GIT -eq 1 ] ; then git init cat >> .git/config <> .gitattributes fi cat < "${DPW_AGE_KEY}.pub" > "${DPW_AGE_DIR}/.recipients" echo "$recipients" | tr ':' '\n' >> "${DPW_AGE_DIR}/.recipients" _git_commit echo "Age Password store initialized in ${DPW_AGE_DIR}" } act="$1"; shift case $act in show) show "$@" ;; list) list "$@" ;; insert) insert "$@" ;; rm) remove "$@" ;; init) _init "$@" ;; *) echo "Bad command $act"; exit 1; ;; esac