[Author Prev][Author Next][Thread Prev][Thread Next][Author Index][Thread Index]

[tor-commits] [bridgedb/master] 5463 - Adds GPG clearsign to email distributor.



commit ca5398470108b8f9444b5c0f823411eb735dcfd0
Author: aagbsn <aagbsn@xxxxxxxx>
Date:   Wed Jun 20 05:55:49 2012 -0700

    5463 - Adds GPG clearsign to email distributor.
    
    Two new configuration options are added to bridgedb.conf:
    
    EMAIL_GPG_SIGNING_ENABLED
    EMAIL_GPG_SIGNING_KEY
    
    The former may be either True or False, and the latter must
    point to the ascii-armored key file. The keyfile must not be
    passphrase protected.
    
    The gpgme library will add the secret key to the secret key ring of
    user who runs BridgeDB.
---
 README                 |   16 ++++++++--
 bridgedb.conf          |    4 ++
 lib/bridgedb/Main.py   |    2 +
 lib/bridgedb/Server.py |   79 +++++++++++++++++++++++++++++++++++++++++++----
 4 files changed, 91 insertions(+), 10 deletions(-)

diff --git a/README b/README
index 6ab1545..baca007 100644
--- a/README
+++ b/README
@@ -20,10 +20,11 @@ To set up:
    - A recaptcha.net account is required. 
    - Install these required packages:
      - Debian: apt-get install python-recaptcha python-beautifulsoup
-     - Python: pip install recaptcha-client BeautifulSoup
+       python-gpgme
+     - Python: pip install recaptcha-client BeautifulSoup pygpgme
      - Others: http://pypi.python.org/pypi/recaptcha-client
-             : http://pypi.python.org/pypi/BeautifulSoup
-
+               http://pypi.python.org/pypi/BeautifulSoup
+               http://pypi.python.org/pypi/pygpgme
 
 To re-generate and update the i18n files (in case translated strings
 have changed in BridgeDB):
@@ -67,6 +68,15 @@ To indicate which bridges are blocked:
  - If this file is present, bridgedb will filter blocked bridges from responses
  - For GeoIP support make sure to install Maxmind GeoIP
 
+To sign emails with gpg:
+ - Add these two options to your bridgedb.conf:
+
+            EMAIL_GPG_SIGNING_ENABLED, EMAIL_GPG_SIGNING_KEY
+    
+   The former may be either True or False, and the latter must
+   point to the ascii-armored key file. The keyfile must not be
+   passphrase protected.
+
 To update the SQL schema:
  - Install sqlite3:
    - Debian: apt-get install sqlite3
diff --git a/bridgedb.conf b/bridgedb.conf
index f044ee6..2b97d78 100644
--- a/bridgedb.conf
+++ b/bridgedb.conf
@@ -146,6 +146,10 @@ EMAIL_N_BRIDGES_PER_ANSWER=3
 # once we have the vidalia/tor interaction fixed for everbody.
 EMAIL_INCLUDE_FINGERPRINTS=False
 
+# Configuration options for GPG signed messages
+EMAIL_GPG_SIGNING_ENABLED = False
+EMAIL_GPG_SIGNING_KEY = ''
+
 #==========
 # Options related to unallocated bridges.
 
diff --git a/lib/bridgedb/Main.py b/lib/bridgedb/Main.py
index 69a4761..977f37a 100644
--- a/lib/bridgedb/Main.py
+++ b/lib/bridgedb/Main.py
@@ -97,6 +97,8 @@ CONFIG = Conf(
     EMAIL_INCLUDE_FINGERPRINTS = False,
     EMAIL_SMTP_HOST="127.0.0.1",
     EMAIL_SMTP_PORT=25,
+    EMAIL_GPG_SIGNING_ENABLED = False,
+    EMAIL_GPG_SIGNING_KEY = "bridgedb-gpg.sec",
 
     RESERVED_SHARE=2,
 
diff --git a/lib/bridgedb/Server.py b/lib/bridgedb/Server.py
index 8ce9ce2..5c4641c 100644
--- a/lib/bridgedb/Server.py
+++ b/lib/bridgedb/Server.py
@@ -6,7 +6,7 @@
 This module implements the web and email interfaces to the bridge database.
 """
 
-from cStringIO import StringIO
+from StringIO import StringIO
 import MimeWriter
 import rfc822
 import time
@@ -31,12 +31,15 @@ from random import randint
 from bridgedb.Raptcha import Raptcha
 import base64
 import textwrap
+
 from ipaddr import IPv4Address, IPv6Address
 from bridgedb.Dist import BadEmail, TooSoonEmail, IgnoreEmail
 
 from bridgedb.Filters import filterBridgesByIP6
 from bridgedb.Filters import filterBridgesByIP4
 from bridgedb.Filters import filterBridgesByTransport
+
+import gpgme
  
 try:
     import GeoIP
@@ -458,7 +461,8 @@ def getMailResponse(lines, ctx):
         # Compose a warning email
         # MAX_EMAIL_RATE is in seconds, convert to hours
         body  = buildSpamWarningTemplate(t) % (bridgedb.Dist.MAX_EMAIL_RATE / 3600)
-        return composeEmail(ctx.fromAddr, clientAddr, subject, body, msgID)
+        return composeEmail(ctx.fromAddr, clientAddr, subject, body, msgID,
+                gpgContext=ctx.gpgContext)
 
     except IgnoreEmail, e:
         logging.info("Got a mail too frequently; ignoring %r: %s.",
@@ -483,7 +487,8 @@ def getMailResponse(lines, ctx):
 
     body = buildMessageTemplate(t) % answer
     # Generate the message.
-    return composeEmail(ctx.fromAddr, clientAddr, subject, body, msgID)
+    return composeEmail(ctx.fromAddr, clientAddr, subject, body, msgID,
+            gpgContext=ctx.gpgContext)
 
 def buildMessageTemplate(t):
     msg_template =  t.gettext(I18n.BRIDGEDB_TEXT[5]) + "\n\n" \
@@ -575,6 +580,9 @@ class MailContext:
         # The number of bridges to send for each email.
         self.N = cfg.EMAIL_N_BRIDGES_PER_ANSWER
 
+        # Initialize a gpg context or set to None for backward compatibliity.
+        self.gpgContext = getGPGContext(cfg)
+
         self.cfg = cfg
 
 class MailMessage:
@@ -690,7 +698,9 @@ def getCCFromRequest(request):
         return path.lower()
     return None 
 
-def composeEmail(fromAddr, clientAddr, subject, body, msgID=False):
+def composeEmail(fromAddr, clientAddr, subject, body, msgID=False,
+        gpgContext=None):
+
     f = StringIO()
     w = MimeWriter.MimeWriter(f)
     w.addheader("From", fromAddr)
@@ -702,10 +712,65 @@ def composeEmail(fromAddr, clientAddr, subject, body, msgID=False):
         w.addheader("In-Reply-To", msgID)
     w.addheader("Date", twisted.mail.smtp.rfc822date())
     mailbody = w.startbody("text/plain")
-    mailbody.write(body)
 
-    f.seek(0)
-    logging.debug(f.readlines())
+    # gpg-clearsign messages
+    if gpgContext:
+        signature = StringIO()
+        plaintext = StringIO(body)
+        sigs = gpgContext.sign(plaintext, signature, gpgme.SIG_MODE_CLEAR)
+        if (len(sigs) != 1):
+            logging.warn('Failed to sign message!')
+        signature.seek(0)
+        [mailbody.write(l) for l in signature]
+    else:
+        mailbody.write(body)
+
     f.seek(0)
     logging.info("Email looks good; we should send an answer.")
     return clientAddr, f
+
+def getGPGContext(cfg):
+    """ 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 unable to initialize.
+
+    The key should not be protected by a passphrase.
+    """
+    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
+
+    try:
+        # import the key
+        keyfile = open(cfg.EMAIL_GPG_SIGNING_KEY)
+        logging.debug("Opened GPG Keyfile %s" % cfg.EMAIL_GPG_SIGNING_KEY)
+        ctx = gpgme.Context()
+        result = ctx.import_(keyfile)
+
+        assert len(result.imports) == 1
+        fingerprint = result.imports[0][0]
+        keyfile.close()
+        logging.debug("GPG Key with fingerprint %s imported" % fingerprint)
+
+        ctx.armor = True
+        ctx.signers = [ctx.get_key(fingerprint)]
+        assert len(ctx.signers) == 1
+
+        # make sure we can sign
+        message = StringIO('Test')
+        signature = StringIO()
+        new_sigs = ctx.sign(message, signature, gpgme.SIG_MODE_CLEAR)
+        assert len(new_sigs) == 1
+
+        # return the ctx
+        return ctx
+
+    except IOError, e:
+        # exit noisily if keyfile not found
+        exit(e)
+    except AssertionError:
+        # exit noisily if key does not pass tests
+        exit('Invalid GPG Signing Key')



_______________________________________________
tor-commits mailing list
tor-commits@xxxxxxxxxxxxxxxxxxxx
https://lists.torproject.org/cgi-bin/mailman/listinfo/tor-commits