commit a87eb73576cd23a9891c69448e44a38732cd963f Author: Isis Lovecruft isis@torproject.org Date: Sun Feb 22 10:52:15 2015 +0000
Remove old GPGME-based GnuPG functions from bridgedb.crypto.
* REMOVE PythonicGpgmeError. * REMOVE LessCrypticGPGMEError. * REMOVE _createGPGMEErrorInterpreters. * REMOVE gpgmeErrorTranslations. * REMOVE getGPGContext. * REMOVE gpgSignMessage. * REMOVE all associated tests. --- lib/bridgedb/crypto.py | 208 -------------------------------------- lib/bridgedb/test/test_crypto.py | 202 ------------------------------------ 2 files changed, 410 deletions(-)
diff --git a/lib/bridgedb/crypto.py b/lib/bridgedb/crypto.py index 724d649..a16e45a 100644 --- a/lib/bridgedb/crypto.py +++ b/lib/bridgedb/crypto.py @@ -41,7 +41,6 @@ Module Overview from __future__ import absolute_import from __future__ import unicode_literals
-import gpgme import hashlib import hmac import io @@ -93,46 +92,6 @@ class PKCS1PaddingError(Exception): class RSAKeyGenerationError(Exception): """Raised when there was an error creating an RSA keypair."""
-class PythonicGpgmeError(Exception): - """Replacement for ``gpgme.GpgmeError`` with understandable error info.""" - -class LessCrypticGPGMEError(Exception): - """Holds interpreted info on source/type of a ``gpgme.GpgmeError``.""" - - def __init__(self, gpgmeError, *args): - self.interpretCrypticGPGMEError(gpgmeError) - super(LessCrypticGPGMEError, self).__init__(self.message) - - def interpretCrypticGPGMEError(self, gpgmeError): - """Set our ``message`` attribute with a decoded explanation of the - GPGME error code received. - - :type gpgmeError: ``gpgme.GpgmeError`` - :param gpgmeError: An exception raised by the gpgme_ module. - - .. _gpgme: https://bazaar.launchpad.net/~jamesh/pygpgme/trunk/view/head:/src/pygpgme-er... - """ - try: - errorSource, errorCode, errorMessage = gpgmeError.args - except (AttributeError, ValueError): - self.message = "Could not get error code from gpgme.GpgmeError!" - return - - if errorCode and errorSource: - try: - sources = gpgmeErrorTranslations[str(errorSource)] - except KeyError: - sources = ['UNKNOWN'] - sources = ', '.join(sources).strip(',') - - try: - names = gpgmeErrorTranslations[str(errorCode)] - except KeyError: - names = ['UNKNOWN'] - names = ', '.join(names).strip(',') - - self.message = "GpgmeError: {0} stemming from {1}: '{2}'""".format( - names, sources, str(errorMessage))
def writeKeyToFile(key, filename): """Write **key** to **filename**, with ``0400`` permissions. @@ -314,181 +273,14 @@ def removePKCS1Padding(message):
return unpadded
-def _createGPGMEErrorInterpreters(): - """Create a mapping of GPGME ERRNOs ←→ human-readable error names/causes.
- This function is called automatically when :mod:`this module - <bridgedb.crypto>` is loaded. The resulting dictionary mapping is stored - as :attr:`~bridgedb.crypto.gpgmeErrorTranslations`, and is used by - :exc:`~bridgedb.crypto.LessCrypticGPGMEError`.
- :returns: A dict of:: - {str(ERRNO): [ERRORNAME, ANOTHER_ERRORNAME, …], - …, - str(ERRORNAME): str(ERRNO), - …} - for all known error numbers and error names/causes. - """ - errorDict = {} - errorAttrs = [] - - if gpgme is not None: - errorAttrs = dir(gpgme) - - for attr in errorAttrs: - if attr.startswith('ERR'): - errorName = attr - errorCode = getattr(gpgme, attr, None) - if errorCode is not None: - try: - allErrorNames = errorDict[str(errorCode)] - except KeyError: - allErrorNames = [] - allErrorNames.append(str(errorName)) - - errorDict.update({str(errorCode): allErrorNames}) - errorDict.update({str(errorName): str(errorCode)}) - - return errorDict - -#: This is a dictionary which holds a translation of GPGME ERRNOs ←→ all known -#: names/causes for that ERRNO, and vice versa. It is created automatically, -#: via the :func:`_createGPGMEErrorInterpreters` function, when this module is -#: loaded so that :exc:`LessCrypticGPGMEError` can use it to display -#: human-readable information about why GPGME borked itself on something. -gpgmeErrorTranslations = _createGPGMEErrorInterpreters() - -def getGPGContext(cfg): - """Import a key from a file and initialise a context for GnuPG operations. - - The key should not be protected by a passphrase, and should have the - signing flag enabled. - - :type cfg: :class:`bridgedb.persistent.Conf` - :param cfg: The loaded config file. - :rtype: :class:`gpgme.Context` or None - :returns: A GPGME context with the signers initialized by the keyfile - specified by the option EMAIL_GPG_SIGNING_KEY in bridgedb.conf, or - None if the option was not enabled, or was unable to initialize. - """ - try: - # must have enabled signing and specified a key file - if not cfg.EMAIL_GPG_SIGNING_ENABLED or not cfg.EMAIL_GPG_SIGNING_KEY: - return None - except AttributeError: - return None
- keyfile = None - ctx = gpgme.Context()
- try: - binary = GPGME_CONTEXT_BINARY[0] - except Exception: - # Setting this to ``None`` will cause libgpgme to "use the default - # binary", according their docs: - binary = None
- try: - homedir = os.path.abspath(GPGME_CONTEXT_HOMEDIR) - logging.debug("Setting GPG homedir to %r" % homedir) - if not os.path.isdir(homedir): - os.makedirs(homedir) - # This is done to ensure that we don't ever use keys in the process - # owner's $GNUPGHOME directory, see: - # http://www.gnupg.org/documentation/manuals/gpgme/Crypto-Engine.html#Crypto-E... - ctx.set_engine_info(gpgme.PROTOCOL_OpenPGP, binary, homedir) - - logging.debug("Opening GPG keyfile %s..." % cfg.EMAIL_GPG_SIGNING_KEY) - keyfile = open(cfg.EMAIL_GPG_SIGNING_KEY) - key = ctx.import_(keyfile) - - if not len(key.imports) > 0: - logging.debug("Unexpected result from gpgme.Context.import_(): %r" - % key) - raise PythonicGpgmeError("Could not import GnuPG key from file %r" - % cfg.EMAIL_GPG_SIGNING_KEY) - - fingerprint = key.imports[0][0] - subkeyFingerprints = [] - # For some reason, if we don't do it exactly like this, we can get - # signatures for *any* key in the current process owner's keyring - # file: - bridgedbKey = ctx.get_key(fingerprint) - bridgedbUID = bridgedbKey.uids[0].uid - logging.info("GnuPG key imported: %s" % bridgedbUID) - logging.info(" Fingerprint: %s" % fingerprint) - for subkey in bridgedbKey.subkeys: - logging.info("Subkey fingerprint: %s" % subkey.fpr) - subkeyFingerprints.append(subkey.fpr) - - ctx.armor = True - ctx.signers = (bridgedbKey,) - - logging.debug("Testing signature created with GnuPG key...") - signatureText, sigs = gpgSignMessage(ctx, "Testing 1 2 3") - - if not len(sigs) == 1: - raise PythonicGpgmeError("Testing couldn't produce a signature "\ - "with GnuPG key: %s" % fingerprint) - - sigFingerprint = sigs[0].fpr - if sigFingerprint in subkeyFingerprints: - logging.info("GPG signatures will use subkey with fingerprint: %s" - % sigFingerprint) - else: - if sigFingerprint != fingerprint: - raise PythonicGpgmeError( - "Test sig fingerprint '%s' not from any appropriate key!" - % sigFingerprint) - - except (IOError, OSError) as error: - logging.debug(error) - logging.error("Could not open or read from GnuPG key file %r!" - % cfg.EMAIL_GPG_SIGNING_KEY) - ctx = None - except gpgme.GpgmeError as error: - lessCryptic = LessCrypticGPGMEError(error) - logging.error(lessCryptic) - ctx = None - except PythonicGpgmeError as error: - logging.error(error) - ctx = None - finally: - if keyfile and not keyfile.closed: - keyfile.close() - - return ctx - -def gpgSignMessage(gpgmeCtx, messageString, mode=None): - """Sign a **messageString** with a GPGME context. - - :param gpgmeCtx: A ``gpgme.Context`` initialised with the appropriate - settings. - :param str messageString: The message to sign. - :param mode: The signing mode. (default: ``gpgme.SIG_MODE_CLEAR``) - :rtype: tuple - :returns: A 2-tuple of ``(signature, list)``, where: - * ``signature`` is the ascii-armored signature text. - * ``list`` is a list of ``gpgme.NewSignature``s. - - .. warning:: The returned signature text and list *may* be empty, if no - signature was created. - """ - if not mode: - mode = gpgme.SIG_MODE_CLEAR
- if NEW_BUFFER_INTERFACE: - msgFile = io.BytesIO(buffer(messageString)) - sigFile = io.BytesIO() - else: - msgFile = io.StringIO(unicode(messageString)) - sigFile = io.StringIO()
- sigList = gpgmeCtx.sign(msgFile, sigFile, mode) - sigFile.seek(0) - signature = sigFile.read()
- return (signature, sigList)
class SSLVerifyingContextFactory(ssl.CertificateOptions): diff --git a/lib/bridgedb/test/test_crypto.py b/lib/bridgedb/test/test_crypto.py index 801a369..da93f07 100644 --- a/lib/bridgedb/test/test_crypto.py +++ b/lib/bridgedb/test/test_crypto.py @@ -15,7 +15,6 @@ from __future__ import print_function from __future__ import unicode_literals
import base64 -import gpgme import io import logging import math @@ -24,7 +23,6 @@ import shutil
import OpenSSL
- from twisted.internet import defer from twisted.trial import unittest from twisted.test.proto_helpers import StringTransport @@ -286,143 +284,6 @@ class RemovePKCS1PaddingTests(unittest.TestCase): self.blob)
-class LessCrypticGPGMEErrorTests(unittest.TestCase): - """Unittests for :class:`bridgedb.crypto.LessCrypticGPGMEError`.""" - - def test_error1(self): - """libgpgme will raise an error when given an io.StringIO for the - message or sigfile. - """ - message = io.StringIO(unicode(self.id())) - sigfile = io.StringIO() - - lessCryptic = None - ctx = gpgme.Context() - - try: - ctx.sign(message, sigfile) - except gpgme.GpgmeError as error: - lessCryptic = crypto.LessCrypticGPGMEError(error) - - self.assertTrue('Invalid argument' in lessCryptic.message) - - def test_noGpgmeErrorArgs(self): - """A gpgme.GpgmeError() without error code args should result in a - 'Could not get error code from gpgme.GpgmeError!' message. - """ - error = gpgme.GpgmeError() - lessCryptic = crypto.LessCrypticGPGMEError(error) - self.assertEqual(lessCryptic.message, - 'Could not get error code from gpgme.GpgmeError!') - - def test_unknownErrorSource(self): - """A gpgme.GpgmeError() without a recognisable error source should say - that the error source is 'UNKNOWN'. - """ - msg = "These numbers make more sense than libgpgme's error codes." - error = gpgme.GpgmeError(math.pi, math.e, msg) - lessCryptic = crypto.LessCrypticGPGMEError(error) - self.assertSubstring('UNKNOWN', lessCryptic.message) - self.assertSubstring(msg, lessCryptic.message) - - def test_unknownErrorCode(self): - """A gpgme.GpgmeError() without a recognisable error code should say - that the error code is 'UNKNOWN'. - """ - msg = "These numbers make more sense than libgpgme's error codes." - error = gpgme.GpgmeError(math.pi, math.e, msg) - lessCryptic = crypto.LessCrypticGPGMEError(error) - self.assertSubstring('UNKNOWN', lessCryptic.message) - self.assertSubstring(msg, lessCryptic.message) - - -class GPGContextTests(unittest.TestCase): - """Tests for :func:`bridgedb.crypto.getGPGContext`.""" - - timeout = 15 - - @fileCheckDecorator - def doCopyFile(self, src, dst, description=None): - shutil.copy(src, dst) - - def removeRundir(self): - """Remove the rundir from the _trial_tmp directory.""" - if os.path.isdir(self.runDir): - shutil.rmtree(self.runDir) - - def makeBadKey(self): - """Make a bad keyfile and set its path in our config.""" - keyfile = os.path.join(self.runDir, 'badkey.asc') - with open(keyfile, 'w') as badkey: - badkey.write(str('NO PASARAN, DEATH CAKES!')) - badkey.flush() - self.setKey(keyfile) - - def enableSigning(self, enable=True): - """Enable or disable the config setting for email signing.""" - setattr(self.config, 'EMAIL_GPG_SIGNING_ENABLED', enable) - - def setKey(self, keyfile=''): - """Set the config keyfile path to **keyfile**.""" - setattr(self.config, 'EMAIL_GPG_SIGNING_KEY', keyfile) - - def setUp(self): - here = os.getcwd() - topDir = here.rstrip('_trial_temp') - self.runDir = os.path.join(here, 'rundir') - self.gpgFile = os.path.join(topDir, 'gnupghome', 'TESTING.subkeys.sec') - self.gpgExpr = os.path.join(topDir, 'gnupghome', - 'TESTING.subkeys.sec.EXPIRED-2013-09-11') - - if not os.path.isdir(self.runDir): - os.makedirs(self.runDir) - - self.config = Conf() - self.enableSigning() - self.addCleanup(self.enableSigning) - self.addCleanup(self.removeRundir) - - def test_getGPGContext_good_keyfile(self): - """Test EmailServer.getGPGContext() with a good key filename.""" - self.setKey(self.gpgFile) - ctx = crypto.getGPGContext(self.config) - self.assertIsInstance(ctx, gpgme.Context) - - def test_getGPGContext_missing_keyfile(self): - """Test EmailServer.getGPGContext() with a missing key filename.""" - self.setKey('missing-keyfile.asc') - ctx = crypto.getGPGContext(self.config) - self.assertTrue(ctx is None) - - def test_getGPGContext_bad_keyfile(self): - """Test EmailServer.getGPGContext() with a missing key filename.""" - self.makeBadKey() - ctx = crypto.getGPGContext(self.config) - self.assertTrue(ctx is None) - - def test_getGPGContext_expired_keyfile(self): - """getGPGContext() with an expired key should return None.""" - self.setKey(self.gpgExpr) - ctx = crypto.getGPGContext(self.config) - self.assertTrue(ctx is None) - - def test_getGPGContext_signing_disabled(self): - """getGPGContext() with signing disabled should return None.""" - self.setKey(self.gpgFile) - self.enableSigning(False) - ctx = crypto.getGPGContext(self.config) - self.assertIsNone(ctx) - - def test_getGPGContext_config_signing_missing(self): - """getGPGContext() with a missing/unset 'EMAIL_GPG_SIGNING_ENABLED' - config line should return None. - """ - self.setKey(self.gpgFile) - delattr(self.config, 'EMAIL_GPG_SIGNING_ENABLED') - ctx = crypto.getGPGContext(self.config) - self.assertIsNone(ctx) - - class SSLVerifyingContextFactoryTests(unittest.TestCase, txtagent.FakeReactorAndConnectMixin): """Tests for :class:`bridgedb.crypto.SSLVerifyingContextFactory`.""" @@ -503,66 +364,3 @@ class SSLVerifyingContextFactoryTests(unittest.TestCase, contextFactory = crypto.SSLVerifyingContextFactory(self.url) self.assertIsInstance(contextFactory.getContext(), OpenSSL.SSL.Context) - - -class GetGPGContextTest(unittest.TestCase): - """Unittests for :func:`bridgedb.crypto.getGPGContext`.""" - - timeout = 15 - - @fileCheckDecorator - def doCopyFile(self, src, dst, description=None): - shutil.copy(src, dst) - - def removeRundir(self): - if os.path.isdir(self.runDir): - shutil.rmtree(self.runDir) - - def makeBadKey(self): - self.setKey(self.badKeyfile) - - def setKey(self, keyfile=''): - setattr(self.config, 'EMAIL_GPG_SIGNING_KEY', keyfile) - - def setUp(self): - here = os.getcwd() - topDir = here.rstrip('_trial_temp') - self.runDir = os.path.join(here, 'rundir') - self.gpgMoved = os.path.join(here, 'TESTING.subkeys.sec') - self.gpgFile = os.path.join(topDir, 'gnupghome', - 'TESTING.subkeys.sec') - - if not os.path.isdir(self.runDir): - os.makedirs(self.runDir) - - self.badKeyfile = os.path.join(here, 'badkey.asc') - with open(self.badKeyfile, 'w') as badkey: - badkey.write('NO PASARAN, DEATH CAKES!') - badkey.flush() - - self.doCopyFile(self.gpgFile, self.gpgMoved, "GnuPG test keyfile") - - self.config = Conf() - setattr(self.config, 'EMAIL_GPG_SIGNING_ENABLED', True) - setattr(self.config, 'EMAIL_GPG_SIGNING_KEY', - 'gnupghome/TESTING.subkeys.sec') - - self.addCleanup(self.removeRundir) - - def test_getGPGContext_good_keyfile(self): - """Test EmailServer.getGPGContext() with a good key filename.""" - self.setKey(self.gpgMoved) - ctx = crypto.getGPGContext(self.config) - self.assertIsInstance(ctx, crypto.gpgme.Context) - - def test_getGPGContext_missing_keyfile(self): - """Test EmailServer.getGPGContext() with a missing key filename.""" - self.setKey('missing-keyfile.asc') - ctx = crypto.getGPGContext(self.config) - self.assertTrue(ctx is None) - - def test_getGPGContext_bad_keyfile(self): - """Test EmailServer.getGPGContext() with a missing key filename.""" - self.makeBadKey() - ctx = crypto.getGPGContext(self.config) - self.assertTrue(ctx is None)