Commit ca539847 authored by aagbsn's avatar aagbsn
Browse files

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.
parent 614cedb7
......@@ -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
......
......@@ -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.
......
......@@ -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,
......
......@@ -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')
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment