Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion README
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,21 @@
features
- encryption implemented using age[1]
- automatic key generation
- yubikey, secure enclave and TPM support
- automatic git tracking
- multiple identity/recipient support
- written in portable posix shell
- simple to extend
- only ~180 lines of code
- only 250 lines of code
- pronounced "pah" - as in "papa"


dependencies
- age
- age-keygen
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

technically age-keygen is optional now as it can be substituted with plugins below, but since it's expected to be delivered with age I don't think it matters (and it's always used as a default fallback)

- age-plugin-yubikey (optional)
- age-plugin-se (optional)
- age-plugin-tpm (optional)
- git (optional)


Expand Down
85 changes: 43 additions & 42 deletions contrib/pa-rekey
Original file line number Diff line number Diff line change
@@ -1,64 +1,65 @@
#!/bin/sh
#
# rotate keys and reencrypt passwords
#
# Reuse identities file: export PA_IDENTITIES=~/.local/share/pa/identities
# Reuse recipients file: export PA_RECIPIENTS=~/.local/share/pa/recipients

die() {
printf '%s: %s.\n' "$(basename "$0")" "$1" >&2
exit 1
}
set -e

age=$(command -v age || command -v rage) ||
die "age not found, install per https://age-encryption.org"
# Complete any pending
# recipients synchronization.
pa l >/dev/null

age_keygen=$(command -v age-keygen || command -v rage-keygen) ||
die "age-keygen not found, install per https://age-encryption.org"
: "${PA_DIR:=${XDG_DATA_HOME:-$HOME/.local/share}/pa}"

# Restrict permissions of any new files to only the current user.
umask 077
suffix=$(LC_ALL=C tr -dc A-Za-z0-9 </dev/urandom |
dd ibs=1 obs=1 count=10 2>/dev/null)

: "${PA_DIR:=${XDG_DATA_HOME:-$HOME/.local/share}/pa}"
[ "$suffix" ]

tmp=$PA_DIR/rekey.$suffix

realstore=$(realpath "$PA_DIR/passwords") ||
die "couldn't get path to password directory"
mkdir "$tmp"

tmpdir=$PA_DIR/tmp
trap 'rm -f "$tmp/identities" "$tmp/recipients" "$tmp/passwords/.recipients"
rmdir "$tmp/passwords" "$tmp"' EXIT

mkdir "$tmpdir" ||
die "couldn't create temporary directory"
cp -p "$PA_DIR/identities" "$tmp/identities"

trap 'rm -rf "$tmpdir"; exit' EXIT
trap 'rm -rf "$tmpdir"; trap - INT; kill -s INT 0' INT
# Generate recipients for current identities.
PA_DIR=$tmp PA_NOGIT='' pa l >/dev/null

cp -Rp "$realstore" "$tmpdir/passwords" ||
die "couldn't copy password directory"
# Filter out recipients corresponding to current identities.
{ grep -Fxvf "$tmp/passwords/.recipients" "$PA_DIR/recipients" ||
:; } >"$tmp/recipients"

# Remove git repository for forward secrecy.
rm -rf "$tmpdir/passwords/.git"
rm -f "$tmp/identities" "$tmp/passwords/.recipients"

[ "$PA_IDENTITIES" ] && cp "$PA_IDENTITIES" "$tmpdir/identities"
[ "$PA_RECIPIENTS" ] && cp "$PA_RECIPIENTS" "$tmpdir/recipients"
# Generate a brand new key pair.
PA_DIR=$tmp PA_NOGIT='' pa l >/dev/null

$age_keygen >>"$tmpdir/identities" 2>/dev/null
$age_keygen -y "$tmpdir/identities" >>"$tmpdir/recipients" 2>/dev/null
printf 'add a new generated recipient? [y/N]: ' >&2

pa l | while read -r name; do
pa s "$name" |
$age -R "$tmpdir/recipients" -o "$tmpdir/passwords/$name.age" ||
die "couldn't encrypt $name.age"
done
[ -t 0 ] && trap 'stty echo icanon; trap - INT; kill -s INT 0' INT

trap - INT EXIT
[ -t 0 ] && stty -echo -icanon

rm -rf "$realstore" ||
die "couldn't remove password directory"
answer=$(dd ibs=1 count=1 2>/dev/null)

mv "$tmpdir/passwords" "$realstore"
mv "$tmpdir/identities" "$(realpath "$PA_DIR/identities")"
mv "$tmpdir/recipients" "$(realpath "$PA_DIR/recipients")"
rmdir "$tmpdir"
[ -t 0 ] && stty echo icanon

# Recreate git repository if needed.
printf '%s\n' "${answer:-N}" >&2

case $answer in [yY]) ;; *) exit 1 ;; esac

# Ensure files can be decrypted with both
# identities if store is in a partial state.
cat "$tmp/identities" >>"$PA_DIR/identities"

# Recipients are now ready to use.
mv "$tmp/recipients" "$PA_DIR/recipients"

# Reencrypt all passwords
# for new recipients.
pa l >/dev/null

# Old identity is now safe to remove.
mv "$tmp/identities" "$PA_DIR/identities"
Loading