[pass] [PATCH 1/2] init: respect multiple subkeys during reencryption

duncankburke at gmail.com duncankburke at gmail.com
Sat Sep 20 08:58:34 CEST 2014

From: Duncan Burke <duncankburke at gmail.com>

In the case where an identity has multiple subkeys for encryption, reencryption
was partially broken. To prevent unnecessary reencryption, the current keys
for a file are compared with the expected keys for a given gpg id. This requires
the means to translate a gpg id into a normalised long keyid.

By default, passing the id of a particular subkey as a recipient does not
necessarily cause gpg to encrypt with that key, rather, gpg takes the
parent identity and chooses a subkey independently. From the code in the function
gpgsm_add_to_certlist (gnupg/sm/certlist.c), it appears gpg simply takes the
last subkey in the list with the encryption flag set. gpg can be forced to use
a specific encryption key by appending '!' to the end of the key id.

For example:

pub   4096R/4A241115 2014-04-26 [expires: 2018-09-18]
sub   4096R/D132D31E 2014-09-19
sub   2048R/8CD8DCB7 2014-09-19

Both D132D31E and 8CD8DCB7 have the encryption flag set.

$ pass init 4A241115
$ pass generate foo 12
$ pass generate dir/bar 12

$ gpg -v --list-only .password-store/foo.gpg
Version: GnuPG v2
gpg: armor header:
gpg: public key is 8CD8DCB7

$ pass init D132D31E
Password store initialized for D132D31E

$ pass init D132D31E!
Password store initialized for D132D31E!
dir/bar: reencrypting to 2698F2A9D132D31E
foo: reencrypting to 2698F2A9D132D31E

$ pass init -p dir D132D31E! 8CD8DCB7!
Password store initialized for D132D31E!, 8CD8DCB7!
dir/bar: reencrypting to 2698F2A9D132D31E 71B10D108CD8DCB7

$ pass generate dir/foobar 12
$ gpg -v --list-only .password-store/dir/foobar.gpg
Version: GnuPG v2
gpg: armor header:
gpg: public key is D132D31E
gpg: public key is 8CD8DCB7

Signed-off-by: Duncan Burke <duncankburke at gmail.com>
 src/password-store.sh | 15 ++++++++++++++-
 1 file changed, 14 insertions(+), 1 deletion(-)

diff --git a/src/password-store.sh b/src/password-store.sh
index 2287a46..cf57fd3 100755
--- a/src/password-store.sh
+++ b/src/password-store.sh
@@ -108,7 +108,20 @@ reencrypt_path() {
 				IFS=";" eval 'GPG_RECIPIENTS+=( $group )' # http://unix.stackexchange.com/a/92190
 				unset GPG_RECIPIENTS[$index]
-			gpg_keys="$($GPG --list-keys --keyid-format long "${GPG_RECIPIENTS[@]}" | sed -n 's/sub *.*\/\([A-F0-9]\{16\}\) .*/\1/p' | LC_ALL=C sort -u)"
+			gpg_keys=""
+			for index in "${!GPG_RECIPIENTS[@]}"; do
+				local key_list="$GPG --list-keys --keyid-format long --with-colons ${GPG_RECIPIENTS[$index]}"
+				if [[ ${GPG_RECIPIENTS[$index]: -1} == "!" ]]; then
+					# The exact subkey is specified
+					local gpg_id="$(tr '[:lower:]' '[:upper:]' <<< ${GPG_RECIPIENTS[index]%!})"
+					gpg_keys+=$(${key_list} | sed -n "s/^sub:\\([^:]*:\\)\\{3\\}\\([^:]*${gpg_id}\\):.*$/\\2 /p" | head -n 1)
+				else
+					# gnupg takes the last subkey with encryption enabled
+					# see gpgsm_add_to_certlist (gnupg/sm/certlist.c)
+					gpg_keys+=$(${key_list} | sed -n "s/^sub:\\([^:]*:\\)\\{3\\}\\([^:]*\\):\\([^:]*:\\)\\{6\\}e:$/\\2 /p" | tail -n 1)
+				fi
+			done
+			gpg_keys="$(printf "%s\n" ${gpg_keys[*]} | LC_ALL=C sort -u)"
 		current_keys="$($GPG -v --no-secmem-warning --no-permission-warning --list-only --keyid-format long "$passfile" 2>&1 | cut -d ' ' -f 5 | LC_ALL=C sort -u)"

More information about the Password-Store mailing list