[Author Prev][Author Next][Thread Prev][Thread Next][Author Index][Thread Index]
[bridgedb/master] Move EmailServer.getGPGContext() → crypto.getGPGContext().
commit 7b75cff76edac559e6dbf6f24584e86f948aa597
Author: Isis Lovecruft <isis@xxxxxxxxxxxxxx>
Date: Wed Apr 16 22:33:33 2014 +0000
Move EmailServer.getGPGContext() â?? crypto.getGPGContext().
* ADD new class, `bridgedb.crypto.LessCrypticGpgmeError` for parsing
GPGME errors.
Usually, these errors are just a tuple of integers, i.e.:
(7, 32586, "Unsupported command")
which means absolutely nothing to me, and is not helping with
debugging. It takes a `gpgme.GpgmeError` as its initialisation
argument, and it automatically makes sense of the stupid thing.
Consequently, the getGPGContext() and signing done with GPGME now
have better error handling.
---
lib/bridgedb/EmailServer.py | 66 +------------------
lib/bridgedb/crypto.py | 153 +++++++++++++++++++++++++++++++++++++++++++
2 files changed, 154 insertions(+), 65 deletions(-)
diff --git a/lib/bridgedb/EmailServer.py b/lib/bridgedb/EmailServer.py
index efa7514..eed96bf 100644
--- a/lib/bridgedb/EmailServer.py
+++ b/lib/bridgedb/EmailServer.py
@@ -28,6 +28,7 @@ from zope.interface import implements
from bridgedb import Dist
from bridgedb import I18n
from bridgedb import safelog
+from bridgedb.crypto import getGPGContext
from bridgedb.Filters import filterBridgesByIP6
from bridgedb.Filters import filterBridgesByIP4
from bridgedb.Filters import filterBridgesByTransport
@@ -500,68 +501,3 @@ def addSMTPServer(cfg, dist, sched):
lc = LoopingCall(dist.cleanDatabase)
lc.start(1800, now=False)
return factory
-
-
-
-
-
-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:
- 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 gpgme.GpgmeError("Could not import GnuPG key from file %r"
- % cfg.EMAIL_GPG_SIGNING_KEY)
-
- fingerprint = key.imports[0][0]
- logging.info("GPG Key with fingerprint %s imported" % fingerprint)
-
- ctx.armor = True
- ctx.signers = [ctx.get_key(fingerprint)]
-
- logging.info("Testing signature created with GnuPG key...")
- message = io.StringIO('Test')
- new_sigs = ctx.sign(message, io.StringIO(), gpgme.SIG_MODE_CLEAR)
- if not len(new_sigs) == 1:
- raise gpgme.GpgmeError(
- "Testing was unable to produce a signature with GnuPG key.")
-
- 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:
- logging.exception(error)
- ctx = None
- finally:
- if keyfile and not keyfile.closed:
- keyfile.close()
-
- return ctx
diff --git a/lib/bridgedb/crypto.py b/lib/bridgedb/crypto.py
index 7f6d597..5c68794 100644
--- a/lib/bridgedb/crypto.py
+++ b/lib/bridgedb/crypto.py
@@ -29,8 +29,10 @@
from __future__ import absolute_import
from __future__ import unicode_literals
+import gpgme
import hashlib
import hmac
+import io
import logging
import os
import re
@@ -51,6 +53,46 @@ DIGESTMOD = hashlib.sha1
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-error.c
+ """
+ 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.
@@ -194,6 +236,117 @@ def getHMACFunc(key, hex=True):
return h_tmp.digest()
return hmac_fn
+def _createGPGMEErrorInterpreters():
+ 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
+
+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:
+ 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...")
+ testMessage = "Testing 1 2 3"
+ signatureText, sigs = gpgSignMessage(ctx, testMessage)
+
+ 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
+
class SSLVerifyingContextFactory(ssl.CertificateOptions):
"""``OpenSSL.SSL.Context`` factory which does full certificate-chain and
_______________________________________________
tor-commits mailing list
tor-commits@xxxxxxxxxxxxxxxxxxxx
https://lists.torproject.org/cgi-bin/mailman/listinfo/tor-commits