From 1aabc9b63279960058425a62c0bd06697eaff034 Mon Sep 17 00:00:00 2001 From: Vinay Sajip Date: Sat, 22 Jul 2023 11:27:02 +0100 Subject: [PATCH] Changes for 0.5.1. --- README.rst | 28 ++++++++++++++++++++++++---- docs/index.rst | 14 +++++++++++--- gnupg.py | 42 ++++++++++++------------------------------ test_gnupg.py | 18 ++++++++++++------ 4 files changed, 59 insertions(+), 43 deletions(-) diff --git a/README.rst b/README.rst index a6c3969..daf7506 100644 --- a/README.rst +++ b/README.rst @@ -68,24 +68,44 @@ Change log .. note:: GCnn refers to an issue nn on Google Code. -0.5.1 (future) +0.5.2 (future) -------------- Released: Not yet -* Added TRUST_EXPIRED to trust_keys. Thanks to Leif Liddy for the patch. -* Fix #206: Remove deprecated --always-trust in favour of --trust-model always +0.5.1 +----- + +Released: 2023-07-22 + +* Added ``TRUST_EXPIRED`` to ``trust_keys``. Thanks to Leif Liddy for the patch. + +* Fix #206: Remove deprecated ``--always-trust`` in favour of ``--trust-model always`` * Fix #208: Add ``status_detail`` attribute to result objects which is populated when the status is ``'invalid recipient'`` (encryption/decryption) or ``'invalid signer'`` - (signing). + (signing). This attribute will be set when the result object's ``status`` attribute is + set to ``invalid recipient`` and will contain more information about the failure in the + form of ``reason:ident`` where ``reason`` is a text description of the reason, and + ``ident`` identifies the recipient key. * Add ``scan_keys_mem()`` function to scan keys in a string. Thanks to Sky Moore for the patch. * Fix #214: Handle multiple signatures when one of them is invalid or unverified. +* A ``problems`` attribute was added which holds problems reported by ``gpg`` + during verification. This is a list of dictionaries, one for each reported + problem. Each dictionary will have ``status`` and ``keyid`` keys indicating + the problem and the corresponding key; other information in the dictionaries + will be error specific. + +* Fix #217: Use machine-readable interface to query the ``gpg`` version. Thanks to Justus + Winter for the patch. + +* Added the ability to export keys to a file. Thanks to Leif Liddy for the patch. + 0.5.0 ----- diff --git a/docs/index.rst b/docs/index.rst index 84388e6..54263c0 100755 --- a/docs/index.rst +++ b/docs/index.rst @@ -165,22 +165,23 @@ verbose (defaults to ``False``) Print information (e.g. the gpg command lines, and status messages returned by gpg) to the console. You don't generally need to set this option, since the module uses Python's ``logging`` package to provide more flexible functionality. The - status messages from GPG are quite voluminous, especially during key generation. + status messages from ``gpg`` are quite voluminous, especially during key generation. use_agent (defaults to ``False``) - If specified as True, the ``--use-agent`` parameter is passed to GPG, asking it to + If specified as True, the ``--use-agent`` parameter is passed to ``gpg``, asking it to use any in-memory GPG agent (which remembers your credentials). keyring (defaults to ``None``) If specified, the value is used as the name of the keyring file. The default keyring is not used. A list of paths to keyring files can also be specified. options (defaults to ``None``) If specified, the value should be a list of additional command-line options to - pass to GPG. + pass to ``gpg``. secret_keyring (defaults to ``None``) If specified, the value is used as the name of the secret keyring file. A list of paths to secret keyring files can also be specified. *Note that these files are not used by GnuPG >= 2.1.* env (defaults to ``None``) If specified, the value is used as the environment variables used when calling the GPG + executable. .. versionchanged:: 0.3.4 The ``keyring`` argument can now also be a list of keyring filenames. @@ -198,6 +199,8 @@ env (defaults to ``None``) ``locale.getpreferredencoding()`` or, failing that, ``sys.stdin.encoding``, and failing that, ``utf-8``. +.. versionadded:: 0.5.0 + The ``env`` argument was added. If the ``gpgbinary`` executable cannot be found, a ``ValueError`` is raised in :meth:`GPG.__init__`. @@ -468,6 +471,8 @@ The ``export_keys`` method has some additional keyword arguments: passphrase is to be passed to ``gpg`` via pinentry, you wouldn't pass it here - so specify ``expect_passphrase=False`` in that case. If you don't do that, and don't pass a passphrase, a ``ValueError`` will be raised. +* ``output`` - defaults to ``None``, but if specified, should be the pathname of a file + to which the exported keys should be written. .. versionadded:: 0.3.7 The ``armor`` and ``minimal`` keyword arguments were added. @@ -478,6 +483,9 @@ The ``export_keys`` method has some additional keyword arguments: .. versionadded:: 0.4.2 The ``expect_passphrase`` keyword argument was added. +.. versionadded:: 0.5.1 + The ``output`` keyword argument was added. + .. index:: Key; importing .. index:: Key; receiving diff --git a/gnupg.py b/gnupg.py index 0e8b2f8..449def9 100644 --- a/gnupg.py +++ b/gnupg.py @@ -27,7 +27,7 @@ and so does not work on Windows). Renamed to gnupg.py to avoid confusion with the previous versions. -Modifications Copyright (C) 2008-2022 Vinay Sajip. All rights reserved. +Modifications Copyright (C) 2008-2023 Vinay Sajip. All rights reserved. For the full documentation, see https://docs.red-dove.com/python-gnupg/ or https://gnupg.readthedocs.io/ @@ -43,9 +43,9 @@ import sys import threading -__version__ = '0.5.1.dev0' +__version__ = '0.5.1' __author__ = 'Vinay Sajip' -__date__ = '$23-Aug-2022 16:36:40$' +__date__ = '$22-Jul-2023 16:36:40$' STARTUPINFO = None if os.name == 'nt': # pragma: no cover @@ -317,11 +317,7 @@ def update_sig_info(**kwargs): self.valid = False self.status = 'signature bad' self.key_id, self.username = value.split(None, 1) - self.problems.append({ - 'status': self.status, - 'keyid': self.key_id, - 'user': self.username - }) + self.problems.append({'status': self.status, 'keyid': self.key_id, 'user': self.username}) update_sig_info(keyid=self.key_id, username=self.username, status=self.status) elif key == 'ERRSIG': # pragma: no cover self.valid = False @@ -346,11 +342,7 @@ def update_sig_info(**kwargs): self.status = 'signature expired' self.key_id, self.username = value.split(None, 1) update_sig_info(keyid=self.key_id, username=self.username, status=self.status) - self.problems.append({ - 'status': self.status, - 'keyid': self.key_id, - 'user': self.username - }) + self.problems.append({'status': self.status, 'keyid': self.key_id, 'user': self.username}) elif key == 'GOODSIG': self.valid = True self.status = 'signature good' @@ -379,18 +371,12 @@ def update_sig_info(**kwargs): self.valid = False self.key_id = value self.status = 'no public key' - self.problems.append({ - 'status': self.status, - 'keyid': self.key_id - }) + self.problems.append({'status': self.status, 'keyid': self.key_id}) elif key == 'NO_SECKEY': # pragma: no cover self.valid = False self.key_id = value self.status = 'no secret key' - self.problems.append({ - 'status': self.status, - 'keyid': self.key_id - }) + self.problems.append({'status': self.status, 'keyid': self.key_id}) elif key in ('EXPKEYSIG', 'REVKEYSIG'): # pragma: no cover # signed with expired or revoked key self.valid = False @@ -401,10 +387,7 @@ def update_sig_info(**kwargs): self.key_status = 'signing key was revoked' self.status = self.key_status update_sig_info(status=self.status, keyid=self.key_id) - self.problems.append({ - 'status': self.status, - 'keyid': self.key_id - }) + self.problems.append({'status': self.status, 'keyid': self.key_id}) elif key in ('UNEXPECTED', 'FAILURE'): # pragma: no cover self.valid = False if key == 'UNEXPECTED': @@ -437,10 +420,9 @@ def update_sig_info(**kwargs): # See issue GH-191 self.valid = False self.status = 'signature expected but not found' - elif key in ('DECRYPTION_INFO', 'PLAINTEXT', 'PLAINTEXT_LENGTH', - 'BEGIN_SIGNING', 'KEY_CONSIDERED'): + elif key in ('DECRYPTION_INFO', 'PLAINTEXT', 'PLAINTEXT_LENGTH', 'BEGIN_SIGNING', 'KEY_CONSIDERED'): pass - elif key in ('NEWSIG',): + elif key in ('NEWSIG', ): # Only sent in gpg2. Clear any signature ID, to be set by a following SIG_ID self.signature_id = None else: # pragma: no cover @@ -1618,8 +1600,8 @@ def delete_keys(self, fingerprints, secret=False, passphrase=None, expect_passph expect_passphrase (bool): Whether a passphrase is expected. - exclamation_mode (bool): If specified, a `'!'` is appended to each fingerprint. This deletes only a subkey or - an entire key, depending on what the fingerprint refers to. + exclamation_mode (bool): If specified, a `'!'` is appended to each fingerprint. This deletes only a subkey + or an entire key, depending on what the fingerprint refers to. .. note:: Passphrases diff --git a/test_gnupg.py b/test_gnupg.py index 461c3ff..d85d133 100644 --- a/test_gnupg.py +++ b/test_gnupg.py @@ -2,7 +2,7 @@ """ A test harness for gnupg.py. -Copyright (C) 2008-2022 Vinay Sajip. All rights reserved. +Copyright (C) 2008-2023 Vinay Sajip. All rights reserved. """ import argparse import json @@ -16,6 +16,11 @@ import tempfile import unittest +try: + unicode +except NameError: + unicode = str + try: from unittest import skipIf except ImportError: # pragma: no cover @@ -30,7 +35,7 @@ def skipIf(condition, message): import gnupg __author__ = 'Vinay Sajip' -__date__ = '$23-Aug-2022 16:37:02$' +__date__ = '$22-Jul-2023 16:37:02$' ALL_TESTS = True @@ -726,7 +731,7 @@ def test_scan_keys(self): fd, key_fn = tempfile.mkstemp(prefix='pygpg-test-') os.write(fd, KEYS_TO_IMPORT.encode('ascii')) os.close(fd) - test_files = (key_fn,) + test_files = (key_fn, ) try: for fn in test_files: logger.debug('scanning keys in %s', fn) @@ -746,7 +751,7 @@ def test_scan_keys_mem(self): 'Gary Gross (A test user) ', 'Danny Davis (A test user) ', ]) - for key in (KEYS_TO_IMPORT,): + for key in (KEYS_TO_IMPORT, ): logger.debug('testing scan_keys') data = self.gpg.scan_keys_mem(key) self.assertEqual(0, data.returncode, 'Non-zero return code') @@ -1560,8 +1565,9 @@ def test_multiple_signatures_one_invalid(self): set([ 'test_deletion', 'test_import_and_export', 'test_list_keys_after_generation', 'test_list_signatures', 'test_key_generation_with_invalid_key_type', 'test_key_generation_with_escapes', 'test_key_generation_input', - 'test_key_generation_with_colons', 'test_search_keys', 'test_scan_keys', 'test_scan_keys_mem', 'test_key_trust', 'test_add_subkey', - 'test_add_subkey_with_invalid_key_type', 'test_deletion_subkey', 'test_list_subkey_after_generation' + 'test_key_generation_with_colons', 'test_search_keys', 'test_scan_keys', 'test_scan_keys_mem', + 'test_key_trust', 'test_add_subkey', 'test_add_subkey_with_invalid_key_type', 'test_deletion_subkey', + 'test_list_subkey_after_generation' ]), 'import': set(['test_import_only', 'test_doctest_import_keys']),