From 137d3ac5c5fdaa61baa1442137009750b5208a35 Mon Sep 17 00:00:00 2001 From: Isis Lovecruft Date: Sat, 2 Aug 2014 01:56:20 +0000 Subject: [PATCH 1/3] Fix encrypting to filenames and/or file-like objects. * FIXES Issue#24, which prevented python-gnupg from encrypting to a filename given as a string to the `output` parameter of `gnupg.GPGMeta._encrypt()`. * THANKS TO by Bill Buddington of SecureDrop and Yan Zhu of the Electronic Frontier Foundation (EFF) for finding and reporting the bug. The ticket for this bug can be viewed at: https://github.com/isislovecruft/python-gnupg/issues/24 --- gnupg/_meta.py | 34 ++++++++++++++++++++++++---------- 1 file changed, 24 insertions(+), 10 deletions(-) diff --git a/gnupg/_meta.py b/gnupg/_meta.py index 00c3a68..3aafacd 100644 --- a/gnupg/_meta.py +++ b/gnupg/_meta.py @@ -766,10 +766,10 @@ class GPGBase(object): **recipients** keys. If False, display trust warnings. (default: True) - :param str output: The output file to write to. If not specified, the - encrypted output is returned, and thus should be - stored as an object in Python. For example: - + :type output: str or file-like object + :param output: The output file to write to. If not specified, the + encrypted output is returned, and thus should be stored + as an object in Python. For example: >>> import shutil >>> import gnupg @@ -808,17 +808,23 @@ class GPGBase(object): """ args = [] + ## FIXME: GnuPG appears to ignore the --output directive when being + ## programmatically driven. We'll handle the IO ourselves to fix this + ## for now. + output_filename = None if output: if getattr(output, 'fileno', None) is not None: ## avoid overwrite confirmation message - if getattr(output, 'name', None) is None: - if os.path.exists(output): - os.remove(output) - args.append('--output %s' % output) - else: + if getattr(output, 'name', None) is not None: + output_filename = output.name if os.path.exists(output.name): os.remove(output.name) - args.append('--output %s' % output.name) + #args.append('--output %s' % output.name) + else: + output_filename = output + if os.path.exists(output): + os.remove(output) + #args.append('--output %s' % output) if armor: args.append('--armor') if always_trust: args.append('--always-trust') @@ -877,4 +883,12 @@ class GPGBase(object): self._handle_io(args, data, result, passphrase=passphrase, binary=True) log.debug("\n%s" % result.data) + + if output_filename: + log.info("Writing encrypted output to file: %s" % output_filename) + with open(output_filename, 'w+') as fh: + fh.write(result.data) + fh.flush() + log.info("Encrypted output written successfully.") + return result From 83784657d571a1db855640fabd06bde07ffc55c1 Mon Sep 17 00:00:00 2001 From: Isis Lovecruft Date: Sat, 2 Aug 2014 04:02:15 +0000 Subject: [PATCH 2/3] Add new unittest that tests encryption with a filename output. * ADD test_encryption_to_filename which checks that encrypt(..., output='somefilename.gpg') works correctly (when `output` is a string containing the filename). This tests for the bug reported in Issue #24. https://github.com/isislovecruft/python-gnupg/issues/24 --- gnupg/test/test_gnupg.py | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/gnupg/test/test_gnupg.py b/gnupg/test/test_gnupg.py index 9541896..5154cf0 100755 --- a/gnupg/test/test_gnupg.py +++ b/gnupg/test/test_gnupg.py @@ -990,6 +990,27 @@ know, maybe you shouldn't be doing it in the first place. log.debug("new (from decryption): %r" % ddata) self.assertEqual(data, ddata) + def test_encryption_to_filename(self): + """Test that ``encrypt(..., output='somefile.gpg')`` is successful.""" + with open(os.path.join(_files, 'kat.sec')) as katsec: + self.gpg.import_keys(katsec.read()) + fpr = self.gpg.list_keys('kat')[0]['fingerprint'] + output = os.path.join(self.gpg.homedir, 'test-encryption-to-filename.gpg') + + message_filename = os.path.join(_files, 'cypherpunk_manifesto') + message_file = open(message_filename) + message = message_file.read() + message_file.close() + + encrypted = self.gpg.encrypt(message, fpr, output=output) + self.assertTrue(encrypted.ok) + self.assertTrue(os.path.isfile(output)) + + # Check the contents: + with open(output) as fh: + encrypted_message = fh.read() + log.debug("Encrypted file contains:\n\n%s\n" % encrypted_message) + suites = { 'parsers': set(['test_parsers_fix_unsafe', 'test_parsers_fix_unsafe_semicolon', @@ -1034,7 +1055,8 @@ suites = { 'parsers': set(['test_parsers_fix_unsafe', 'test_encryption_decryption_multi_recipient', 'test_decryption', 'test_symmetric_encryption_and_decryption', - 'test_file_encryption_and_decryption']), + 'test_file_encryption_and_decryption', + 'test_encryption_to_filename',]), 'listkeys': set(['test_list_keys_after_generation']), 'keyrings': set(['test_public_keyring', 'test_secret_keyring', From 4d120a22882738305015587379b49c2aff26b275 Mon Sep 17 00:00:00 2001 From: Isis Lovecruft Date: Sat, 2 Aug 2014 04:14:22 +0000 Subject: [PATCH 3/3] Add unittest which tests encrypt() when `output` is given an open file. * ADD new unittest, `test_encryption_to_filehandle`. --- gnupg/test/test_gnupg.py | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/gnupg/test/test_gnupg.py b/gnupg/test/test_gnupg.py index 5154cf0..46876f5 100755 --- a/gnupg/test/test_gnupg.py +++ b/gnupg/test/test_gnupg.py @@ -1011,6 +1011,28 @@ know, maybe you shouldn't be doing it in the first place. encrypted_message = fh.read() log.debug("Encrypted file contains:\n\n%s\n" % encrypted_message) + def test_encryption_to_filehandle(self): + """Test that ``encrypt(..., output=filelikething)`` is successful.""" + with open(os.path.join(_files, 'kat.sec')) as katsec: + self.gpg.import_keys(katsec.read()) + fpr = self.gpg.list_keys('kat')[0]['fingerprint'] + output = os.path.join(self.gpg.homedir, 'test-encryption-to-filehandle.gpg') + output_file = open(output, 'w+') + + message_filename = os.path.join(_files, 'cypherpunk_manifesto') + message_file = open(message_filename) + message = message_file.read() + message_file.close() + + encrypted = self.gpg.encrypt(message, fpr, output=output_file) + self.assertTrue(encrypted.ok) + self.assertTrue(os.path.isfile(output)) + + # Check the contents: + with open(output) as fh: + encrypted_message = fh.read() + log.debug("Encrypted file contains:\n\n%s\n" % encrypted_message) + suites = { 'parsers': set(['test_parsers_fix_unsafe', 'test_parsers_fix_unsafe_semicolon', @@ -1056,7 +1078,8 @@ suites = { 'parsers': set(['test_parsers_fix_unsafe', 'test_decryption', 'test_symmetric_encryption_and_decryption', 'test_file_encryption_and_decryption', - 'test_encryption_to_filename',]), + 'test_encryption_to_filename', + 'test_encryption_to_filehandle',]), 'listkeys': set(['test_list_keys_after_generation']), 'keyrings': set(['test_public_keyring', 'test_secret_keyring',