[pass] [PATCH] Team pass: enable multiple keys and per directory

Jason A. Donenfeld Jason at zx2c4.com
Wed Mar 19 09:08:27 CET 2014


The .gpg-id file may now have multiple keys in it, one per line.

If a .gpg-id file exists inside a subdirectory, passwords inside that
directory are encrypted to that/those ids.

The init command has learned a -p/--path option for writing such a sub
directory .gpg-id and now can take several arguments for ids.
---

THIS IS A DRAFT

After much demand for a "team pass" that can live within git, and
instead of relying on gpg.conf for this, I've started to implement
a proper team pass.

Please tell me what you think of this. I want to get it right. Does
it satisfy the demands folks have had? Is it implemented as cleanly
as possible? Feedback wanted.


 man/pass.1            | 18 ++++++----
 src/password-store.sh | 95 +++++++++++++++++++++++++++++++++------------------
 2 files changed, 74 insertions(+), 39 deletions(-)

diff --git a/man/pass.1 b/man/pass.1
index efb5d9b..9399dba 100644
--- a/man/pass.1
+++ b/man/pass.1
@@ -51,15 +51,19 @@ password names in
 .SH COMMANDS
 
 .TP
-\fBinit\fP [ \fI--reencrypt\fP, \fI-e\fP ] \fIgpg-id\fP
+\fBinit\fP [ \fI--reencrypt\fP, \fI-e\fP ] [ \fI--path=sub-folder\fP, \fI-p sub-folder\fP ] \fIgpg-id...\fP
 Initialize new password storage and use
 .I gpg-id
-for encryption. This command must be run first before a password store can be
-used. If \fI--reencrypt\fP or \fI-e\fP is specified, reencrypt all existing
-passwords in the password store using \fIgpg-id\fP. Note that use of
+for encryption. Multiple gpg-ids may be specified, in order to encrypt each
+password with multiple ids. This command must be run first before a password
+store can be used. If \fI--reencrypt\fP or \fI-e\fP is specified, reencrypt
+all existing passwords in the password store using \fIgpg-id\fP. Note that
+use of
 .BR gpg-agent (1)
 is recommended so that the batch decryption does not require as much user
-intervention.
+intervention. If \fI--path\fP or \fI-p\fP is specified, along with an argument,
+a specific gpg-id or set of gpg-ids is assigned for that specific sub folder of
+the password store.
 .TP
 \fBls\fP \fIsubfolder\fP
 List names of passwords inside the tree at
@@ -322,7 +326,9 @@ The default password storage directory.
 .TP
 .B ~/.password-store/.gpg-id
 Contains the default gpg key identification used for encryption and decryption.
-This should be set using the \fBinit\fP command.
+Multiple gpg keys may be specified in this file, one per line. If this file
+exists in any sub directories, passwords inside those sub directories are
+encrypted using those keys. This should be set using the \fBinit\fP command.
 
 .SH ENVIRONMENT VARIABLES
 
diff --git a/src/password-store.sh b/src/password-store.sh
index b2e2d0c..3ee23cc 100755
--- a/src/password-store.sh
+++ b/src/password-store.sh
@@ -6,7 +6,6 @@
 umask 077
 
 PREFIX="${PASSWORD_STORE_DIR:-$HOME/.password-store}"
-ID="$PREFIX/.gpg-id"
 GIT_DIR="${PASSWORD_STORE_GIT:-$PREFIX}/.git"
 GPG_OPTS="--quiet --yes --batch --compress-algo=none"
 
@@ -77,6 +76,38 @@ yesno() {
 	read -p "$1 [y/N] " response
 	[[ $response == [yY] ]] || exit 1
 }
+set_gpg_recipients() {
+	gpg_recipient_args=( )
+
+	if [[ -n "$PASSWORD_STORE_KEY" ]]; then
+		for gpg_id in $PASSWORD_STORE_KEY; do
+			gpg_recipient_args+=( "-r" "$gpg_id" )
+		done
+		return
+	fi
+
+	local current="$PREFIX/$1"
+	while true; do
+		[[ "$(realpath -q "$current")" == "$(realpath -q "$PREFIX")" ]] && break
+		[[ -f "$current/.gpg-id" ]] && break
+		current="$current/.."
+	done
+	current="$current/.gpg-id"
+
+	if [[ ! -f $current ]]; then
+		echo "You must run:"
+		echo "    $program init your-gpg-id"
+		echo "before you may use the password store."
+		echo
+		usage
+		exit 1
+	fi
+
+	while read gpg_id; do
+		gpg_recipient_args+=( "-r" "$gpg_id" )
+	done < "$current"
+}
+
 #
 # BEGIN Platform definable
 #
@@ -138,32 +169,43 @@ fi
 case "$command" in
 	init)
 		reencrypt=0
+		id_path=""
 
-		opts="$($GETOPT -o e -l reencrypt -n "$program" -- "$@")"
+		opts="$($GETOPT -o ep: -l reencrypt,path: -n "$program" -- "$@")"
 		err=$?
 		eval set -- "$opts"
 		while true; do case $1 in
 			-e|--reencrypt) reencrypt=1; shift ;;
+			-p|--path) id_path="$2"; shift; shift ;;
 			--) shift; break ;;
 		esac done
 
-		if [[ $# -ne 1 ]]; then
-			echo "Usage: $program $command [--reencrypt,-e] gpg-id"
+		if [[ $# -lt 1 ]]; then
+			echo "Usage: $program $command [--reencrypt,-e] [--path=subfolder,-p subfolder] gpg-id..."
 			exit 1
 		fi
+		if [[ -n "$id_path" && ! -d "$PREFIX/$id_path" ]]; then
+			if [[ -e "$PREFIX/$id_path" ]]; then
+				echo "Error: $PREFIX/$id_path exists but is not a directory."
+				exit 1;
+			fi
+			echo "Creating new sub-folder $id_path"
+		fi
 
-		gpg_id="$1"
-		mkdir -v -p "$PREFIX"
-		echo "$gpg_id" > "$ID"
-		echo "Password store initialized for $gpg_id."
-		git_add_file "$ID" "Set GPG id to $gpg_id."
+		mkdir -v -p "$PREFIX/$id_path"
+		gpg_id="$PREFIX/$id_path/.gpg-id"
+		printf "%s\n" "$@" > "$gpg_id"
+		id_print="$(printf "%s, " "$@" | head -c -2)"
+		echo "Password store initialized for $id_print"
+		git_add_file "$gpg_id" "Set GPG id to ${id_print}."
 
 		if [[ $reencrypt -eq 1 ]]; then
-			find "$PREFIX/" -iname '*.gpg' | while read passfile; do
-				gpg2 -d $GPG_OPTS "$passfile" | gpg2 -e -r "$gpg_id" -o "$passfile.new" $GPG_OPTS &&
+			find "$PREFIX/$id_path" -iname '*.gpg' | while read passfile; do
+				set_gpg_recipients "$(sed "s:^$PREFIX/::" <<<"$(dirname "$passfile")")"
+				gpg2 -d $GPG_OPTS "$passfile" | gpg2 -e "${gpg_recipient_args[@]}" -o "$passfile.new" $GPG_OPTS &&
 				mv -v "$passfile.new" "$passfile"
 			done
-			git_add_file "$PREFIX" "Reencrypted entire store using new GPG id $gpg_id."
+			git_add_file "$PREFIX/$id_path" "Reencrypted password store using new GPG id ${id_print}."
 		fi
 		exit 0
 		;;
@@ -175,22 +217,6 @@ case "$command" in
 		version
 		exit 0
 		;;
-esac
-
-if [[ -n $PASSWORD_STORE_KEY ]]; then
-	ID="$PASSWORD_STORE_KEY"
-elif [[ ! -f $ID ]]; then
-	echo "You must run:"
-	echo "    $program init your-gpg-id"
-	echo "before you may use the password store."
-	echo
-	usage
-	exit 1
-else
-	ID="$(head -n 1 "$ID")"
-fi
-
-case "$command" in
 	show|ls|list)
 		clip=0
 
@@ -254,11 +280,12 @@ case "$command" in
 		[[ $force -eq 0 && -e $passfile ]] && yesno "An entry already exists for $path. Overwrite it?"
 
 		mkdir -p -v "$PREFIX/$(dirname "$path")"
+		set_gpg_recipients "$(dirname "$path")"
 
 		if [[ $multiline -eq 1 ]]; then
 			echo "Enter contents of $path and press Ctrl+D when finished:"
 			echo
-			gpg2 -e -r "$ID" -o "$passfile" $GPG_OPTS
+			gpg2 -e "${gpg_recipient_args[@]}" -o "$passfile" $GPG_OPTS
 		elif [[ $noecho -eq 1 ]]; then
 			while true; do
 				read -r -p "Enter password for $path: " -s password
@@ -266,7 +293,7 @@ case "$command" in
 				read -r -p "Retype password for $path: " -s password_again
 				echo
 				if [[ $password == "$password_again" ]]; then
-					gpg2 -e -r "$ID" -o "$passfile" $GPG_OPTS <<<"$password"
+					gpg2 -e "${gpg_recipient_args[@]}" -o "$passfile" $GPG_OPTS <<<"$password"
 					break
 				else
 					echo "Error: the entered passwords do not match."
@@ -274,7 +301,7 @@ case "$command" in
 			done
 		else
 			read -r -p "Enter password for $path: " -e password
-			gpg2 -e -r "$ID" -o "$passfile" $GPG_OPTS <<<"$password"
+			gpg2 -e "${gpg_recipient_args[@]}" -o "$passfile" $GPG_OPTS <<<"$password"
 		fi
 		git_add_file "$passfile" "Added given password for $path to store."
 		;;
@@ -286,6 +313,7 @@ case "$command" in
 
 		path="$1"
 		mkdir -p -v "$PREFIX/$(dirname "$path")"
+		set_gpg_recipients "$(dirname "$path")"
 		passfile="$PREFIX/$path.gpg"
 		template="$program.XXXXXXXXXXXXX"
 
@@ -300,7 +328,7 @@ case "$command" in
 			action="Edited"
 		fi
 		${EDITOR:-vi} "$tmp_file"
-		while ! gpg2 -e -r "$ID" -o "$passfile" $GPG_OPTS "$tmp_file"; do
+		while ! gpg2 -e "${gpg_recipient_args[@]}" -o "$passfile" $GPG_OPTS "$tmp_file"; do
 			echo "GPG encryption failed. Retrying."
 			sleep 1
 		done
@@ -332,13 +360,14 @@ case "$command" in
 			exit 1
 		fi
 		mkdir -p -v "$PREFIX/$(dirname "$path")"
+		set_gpg_recipients "$(dirname "$path")"
 		passfile="$PREFIX/$path.gpg"
 
 		[[ $force -eq 0 && -e $passfile ]] && yesno "An entry already exists for $path. Overwrite it?"
 
 		pass="$(pwgen -s $symbols $length 1)"
 		[[ -n $pass ]] || exit 1
-		gpg2 -e -r "$ID" -o "$passfile" $GPG_OPTS <<<"$pass"
+		gpg2 -e "${gpg_recipient_args[@]}" -o "$passfile" $GPG_OPTS <<<"$pass"
 		git_add_file "$passfile" "Added generated password for $path to store."
 		
 		if [[ $clip -eq 0 ]]; then
-- 
1.9.0



More information about the Password-Store mailing list