From e79097c090d0a5acb3b8fd947e09bbe4cc7137df Mon Sep 17 00:00:00 2001 From: Tomas Pospisek Date: Tue, 16 Feb 2021 16:44:47 +0100 Subject: [PATCH 01/10] start migration to Python3 --- vault_from_gpg_agent.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vault_from_gpg_agent.py b/vault_from_gpg_agent.py index 8627024..49c8c42 100755 --- a/vault_from_gpg_agent.py +++ b/vault_from_gpg_agent.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 # # Store your Ansible vault password in gpg-agent. # From 38cf8d5584ac1083195b121e26c51cf1e54fcbfa Mon Sep 17 00:00:00 2001 From: Tomas Pospisek Date: Tue, 16 Feb 2021 16:45:20 +0100 Subject: [PATCH 02/10] print(foo) instead of print foo --- vault_from_gpg_agent.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vault_from_gpg_agent.py b/vault_from_gpg_agent.py index 49c8c42..a79bbb2 100755 --- a/vault_from_gpg_agent.py +++ b/vault_from_gpg_agent.py @@ -50,7 +50,7 @@ def get_passphrase(gpg_agent, my_path, cache_id): else: # You'll get an exception here if we get anything we didn't expect. passphrase = stdout[3:-1].decode("hex") - print passphrase + print(passphrase) def clear_passphrase(gpg_agent, cache_id): From d58d2bcfe120e7fe7729a0accf36b9e897b32059 Mon Sep 17 00:00:00 2001 From: Tomas Pospisek Date: Tue, 16 Feb 2021 16:46:58 +0100 Subject: [PATCH 03/10] set encoding before passing to hash function This fixes: $ vault_from_gpg_agent.py --clear Traceback (most recent call last): File "/usr/local/bin/vault_from_gpg_agent.py", line 88, in main() File "/usr/local/bin/vault_from_gpg_agent.py", line 79, in main hashed_path = base64.b64encode(hashlib.sha1(my_path).hexdigest()) TypeError: Unicode-objects must be encoded before hashing I do `my_path.encode('utf-8')` assuming (hoping) that `vault_from_gpg_agent.py`'s path on a system will not be changing over time with respect to encoding. That means `stat $my_path` will always return the same string with the same encoding. If that remains true then I think we can encode `my_path` as we wish, provided that utf-8 can hold all the characters that can occur in a filename... --- vault_from_gpg_agent.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vault_from_gpg_agent.py b/vault_from_gpg_agent.py index a79bbb2..9ed704c 100755 --- a/vault_from_gpg_agent.py +++ b/vault_from_gpg_agent.py @@ -72,7 +72,7 @@ def main(): my_path = os.path.realpath(sys.argv[0]) # Per the source, cache-id is limited to 50 bytes, so we hash our # path and Base64 encode the path. - hashed_path = base64.b64encode(hashlib.sha1(my_path).digest()) + hashed_path = base64.b64encode(hashlib.sha1(my_path.encode('utf-8')).digest()) cache_id = "ansible-vault:%s" % (hashed_path,) if args.clear: clear_passphrase(gpg_agent, cache_id) From f9ff042e6f451bdfe21de3418bdf3bd8053a6899 Mon Sep 17 00:00:00 2001 From: Tomas Pospisek Date: Tue, 16 Feb 2021 16:54:22 +0100 Subject: [PATCH 04/10] define encoding when communicating with the gpg-agent process This fixes: $ vault_from_gpg_agent.py --clear Traceback (most recent call last): File "/usr/local/bin/vault_from_gpg_agent.py", line 91, in main() File "/usr/local/bin/vault_from_gpg_agent.py", line 85, in main clear_passphrase(gpg_agent, cache_id) File "/usr/local/bin/vault_from_gpg_agent.py", line 61, in clear_passphrase stdout = gpg_agent.communicate("CLEAR_PASSPHRASE %s\n" % (cache_id,))[0] File "/usr/lib/python3.7/subprocess.py", line 939, in communicate stdout, stderr = self._communicate(input, endtime, timeout) File "/usr/lib/python3.7/subprocess.py", line 1666, in _communicate input_view = memoryview(self._input) TypeError: memoryview: a bytes-like object is required, not 'str' I set the encoding='utf8' when doing the `Popen`. As long as only ascii characters get passed back and forth we should be safe. --- vault_from_gpg_agent.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vault_from_gpg_agent.py b/vault_from_gpg_agent.py index 9ed704c..f630cb8 100755 --- a/vault_from_gpg_agent.py +++ b/vault_from_gpg_agent.py @@ -67,7 +67,7 @@ def main(): parser.add_argument("--clear", default=False, action="store_true", help="Clear password from GPG agent") gpg_agent = subprocess.Popen(["gpg-connect-agent"], stdin=subprocess.PIPE, - stdout=subprocess.PIPE) + stdout=subprocess.PIPE, encoding='utf8') args = parser.parse_args() my_path = os.path.realpath(sys.argv[0]) # Per the source, cache-id is limited to 50 bytes, so we hash our From cc647e07b920386905a54035b653ac9b7c89c995 Mon Sep 17 00:00:00 2001 From: Tomas Pospisek Date: Tue, 16 Feb 2021 17:17:11 +0100 Subject: [PATCH 05/10] add module namespace Fixes: $ vault_from_gpg_agent.py Traceback (most recent call last): File "/usr/local/bin/vault_from_gpg_agent.py", line 96, in main() File "/usr/local/bin/vault_from_gpg_agent.py", line 92, in main get_passphrase(gpg_agent, my_path, cache_id) File "/usr/local/bin/vault_from_gpg_agent.py", line 53, in get_passphrase description = urllib.quote( AttributeError: module 'urllib' has no attribute 'quote' --- vault_from_gpg_agent.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/vault_from_gpg_agent.py b/vault_from_gpg_agent.py index f630cb8..680839a 100755 --- a/vault_from_gpg_agent.py +++ b/vault_from_gpg_agent.py @@ -31,6 +31,7 @@ import subprocess import sys import urllib +import urllib.parse import os.path import hashlib import base64 @@ -38,7 +39,7 @@ def get_passphrase(gpg_agent, my_path, cache_id): - description = urllib.quote( + description = urllib.parse.quote( "Please enter the ansible vault password for %s" % (my_path,)) command = "GET_PASSPHRASE %s X X %s\n" % (cache_id, description) stdout = gpg_agent.communicate(command)[0] From 0e865ce1009941d03972bd602d9a60a7f6e190ec Mon Sep 17 00:00:00 2001 From: Tomas Pospisek Date: Tue, 16 Feb 2021 18:24:56 +0100 Subject: [PATCH 06/10] do not assume utf-8: exchange bytes a priori --- vault_from_gpg_agent.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vault_from_gpg_agent.py b/vault_from_gpg_agent.py index 680839a..0ea910f 100755 --- a/vault_from_gpg_agent.py +++ b/vault_from_gpg_agent.py @@ -68,7 +68,7 @@ def main(): parser.add_argument("--clear", default=False, action="store_true", help="Clear password from GPG agent") gpg_agent = subprocess.Popen(["gpg-connect-agent"], stdin=subprocess.PIPE, - stdout=subprocess.PIPE, encoding='utf8') + stdout=subprocess.PIPE) args = parser.parse_args() my_path = os.path.realpath(sys.argv[0]) # Per the source, cache-id is limited to 50 bytes, so we hash our From ba012dd2df9079de96d41ddb6cf65edcd0c058bb Mon Sep 17 00:00:00 2001 From: Tomas Pospisek Date: Tue, 16 Feb 2021 18:26:59 +0100 Subject: [PATCH 07/10] encode command as UTF-8 The full path of vault_from_gpg_agent.py can contain the full range of characters. Encode it to UTF-8 as that should encompass the most broad character range. Assume and hope that pinentry will be able to handle UTF-8. I was not able to find documentation that is explicit about what pinentry accepts. The idea here is to preserve as much as possible what the user is seeing of the path and display that to him/her. --- vault_from_gpg_agent.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/vault_from_gpg_agent.py b/vault_from_gpg_agent.py index 0ea910f..67220ba 100755 --- a/vault_from_gpg_agent.py +++ b/vault_from_gpg_agent.py @@ -42,7 +42,10 @@ def get_passphrase(gpg_agent, my_path, cache_id): description = urllib.parse.quote( "Please enter the ansible vault password for %s" % (my_path,)) command = "GET_PASSPHRASE %s X X %s\n" % (cache_id, description) - stdout = gpg_agent.communicate(command)[0] + # my_path might include the full character range thus hope that + # pinentry can handle it (no documentation found about the + # protocol encoding of pinentry) + stdout = gpg_agent.communicate(command.encode('utf-8'))[0] if gpg_agent.returncode != 0: raise Exception("gpg-connect-agent exited %r" % (gpg_agent.returncode,)) From f50aa2a01d8f81a08f4b3431976d93441817ae68 Mon Sep 17 00:00:00 2001 From: Tomas Pospisek Date: Tue, 16 Feb 2021 18:34:21 +0100 Subject: [PATCH 08/10] the 'OK' from gpg-agent should be ascii encoded --- vault_from_gpg_agent.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/vault_from_gpg_agent.py b/vault_from_gpg_agent.py index 67220ba..d5d00ed 100755 --- a/vault_from_gpg_agent.py +++ b/vault_from_gpg_agent.py @@ -49,7 +49,7 @@ def get_passphrase(gpg_agent, my_path, cache_id): if gpg_agent.returncode != 0: raise Exception("gpg-connect-agent exited %r" % (gpg_agent.returncode,)) - elif not stdout.startswith("OK"): + elif not stdout.startswith("OK".encode('ascii')): raise Exception("gpg-agent says: %s" % (stdout.rstrip(),)) else: # You'll get an exception here if we get anything we didn't expect. @@ -62,7 +62,7 @@ def clear_passphrase(gpg_agent, cache_id): if gpg_agent.returncode != 0: raise Exception("gpg-connect-agent exited %r" % (gpg_agent.returncode,)) - elif not stdout.startswith("OK"): + elif not stdout.startswith("OK".encode('ascii')): raise Exception("gog-agent says: %s" % (stdout.rstrip(),)) From 759a388fa12642e4e9343750c1a1afdc998dc7ba Mon Sep 17 00:00:00 2001 From: Tomas Pospisek Date: Tue, 16 Feb 2021 18:35:43 +0100 Subject: [PATCH 09/10] decode hex from ascii transform result to utf-8 --- vault_from_gpg_agent.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vault_from_gpg_agent.py b/vault_from_gpg_agent.py index d5d00ed..4b2fedc 100755 --- a/vault_from_gpg_agent.py +++ b/vault_from_gpg_agent.py @@ -53,7 +53,7 @@ def get_passphrase(gpg_agent, my_path, cache_id): raise Exception("gpg-agent says: %s" % (stdout.rstrip(),)) else: # You'll get an exception here if we get anything we didn't expect. - passphrase = stdout[3:-1].decode("hex") + passphrase = bytes.fromhex(stdout[3:-1].decode('ascii')).decode('utf-8') print(passphrase) From fc73477350a176c8c0705c6669e1bbcc42468453 Mon Sep 17 00:00:00 2001 From: Tomas Pospisek Date: Tue, 16 Feb 2021 18:37:30 +0100 Subject: [PATCH 10/10] use ascii for clearing of passphrase both the command and the cache_id are ascii only --- vault_from_gpg_agent.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/vault_from_gpg_agent.py b/vault_from_gpg_agent.py index 4b2fedc..cbfe287 100755 --- a/vault_from_gpg_agent.py +++ b/vault_from_gpg_agent.py @@ -58,7 +58,9 @@ def get_passphrase(gpg_agent, my_path, cache_id): def clear_passphrase(gpg_agent, cache_id): - stdout = gpg_agent.communicate("CLEAR_PASSPHRASE %s\n" % (cache_id,))[0] + command = "CLEAR_PASSPHRASE %s\n" % (cache_id,) + # cache_id is a hash which is ascii only + stdout = gpg_agent.communicate(command.encode('ascii'))[0] if gpg_agent.returncode != 0: raise Exception("gpg-connect-agent exited %r" % (gpg_agent.returncode,))