summaryrefslogtreecommitdiff
path: root/src/leap/mail/service.py
diff options
context:
space:
mode:
Diffstat (limited to 'src/leap/mail/service.py')
-rw-r--r--src/leap/mail/service.py201
1 files changed, 119 insertions, 82 deletions
diff --git a/src/leap/mail/service.py b/src/leap/mail/service.py
index f6e4d11..a99f13a 100644
--- a/src/leap/mail/service.py
+++ b/src/leap/mail/service.py
@@ -24,7 +24,6 @@ from OpenSSL import SSL
from twisted.mail import smtp
from twisted.internet import reactor
from twisted.internet import defer
-from twisted.internet.threads import deferToThread
from twisted.protocols.amp import ssl
from twisted.python import log
@@ -111,17 +110,17 @@ class OutgoingMail:
:type recipient: smtp.User
:return: a deferred which delivers the message when fired
"""
- d = deferToThread(lambda: self._maybe_encrypt_and_sign(raw, recipient))
+ d = self._maybe_encrypt_and_sign(raw, recipient)
d.addCallback(self._route_msg)
d.addErrback(self.sendError)
-
return d
def sendSuccess(self, smtp_sender_result):
"""
Callback for a successful send.
- :param smtp_sender_result: The result from the ESMTPSender from _route_msg
+ :param smtp_sender_result: The result from the ESMTPSender from
+ _route_msg
:type smtp_sender_result: tuple(int, list(tuple))
"""
dest_addrstr = smtp_sender_result[1][0][0]
@@ -145,7 +144,8 @@ class OutgoingMail:
"""
Sends the msg using the ESMTPSenderFactory.
- :param encrypt_and_sign_result: A tuple containing the 'maybe' encrypted message and the recipient
+ :param encrypt_and_sign_result: A tuple containing the 'maybe'
+ encrypted message and the recipient
:type encrypt_and_sign_result: tuple
"""
message, recipient = encrypt_and_sign_result
@@ -173,7 +173,6 @@ class OutgoingMail:
self._host, self._port, factory,
contextFactory=SSLContextFactory(self._cert, self._key))
-
def _maybe_encrypt_and_sign(self, raw, recipient):
"""
Attempt to encrypt and sign the outgoing message.
@@ -209,16 +208,20 @@ class OutgoingMail:
:param recipient: The recipient for the message
:type: recipient: smtp.User
+ :return: A Deferred that will be fired with a MIMEMultipart message
+ and the original recipient Message
+ :rtype: Deferred
"""
# pass if the original message's content-type is "multipart/encrypted"
lines = raw.split('\r\n')
origmsg = Parser().parsestr(raw)
if origmsg.get_content_type() == 'multipart/encrypted':
- return origmsg
+ return defer.success((origmsg, recipient))
from_address = validate_address(self._from_address)
username, domain = from_address.split('@')
+ to_address = validate_address(recipient.dest.addrstr)
# add a nice footer to the outgoing message
# XXX: footer will eventually optional or be removed
@@ -230,80 +233,93 @@ class OutgoingMail:
origmsg = Parser().parsestr('\r\n'.join(lines))
- # get sender and recipient data
- signkey = self._keymanager.get_key(from_address, OpenPGPKey, private=True)
- log.msg("Will sign the message with %s." % signkey.fingerprint)
- to_address = validate_address(recipient.dest.addrstr)
- try:
- # try to get the recipient pubkey
- pubkey = self._keymanager.get_key(to_address, OpenPGPKey)
- log.msg("Will encrypt the message to %s." % pubkey.fingerprint)
- signal(proto.SMTP_START_ENCRYPT_AND_SIGN,
- "%s,%s" % (self._from_address, to_address))
- newmsg = self._encrypt_and_sign(origmsg, pubkey, signkey)
-
+ def signal_encrypt_sign(newmsg):
signal(proto.SMTP_END_ENCRYPT_AND_SIGN,
"%s,%s" % (self._from_address, to_address))
- except KeyNotFound:
- # at this point we _can_ send unencrypted mail, because if the
- # configuration said the opposite the address would have been
- # rejected in SMTPDelivery.validateTo().
- log.msg('Will send unencrypted message to %s.' % to_address)
- signal(proto.SMTP_START_SIGN, self._from_address)
- newmsg = self._sign(origmsg, signkey)
- signal(proto.SMTP_END_SIGN, self._from_address)
- return newmsg, recipient
+ return newmsg, recipient
+ def signal_sign(newmsg):
+ signal(proto.SMTP_END_SIGN, self._from_address)
+ return newmsg, recipient
+
+ def if_key_not_found_send_unencrypted(failure):
+ if failure.check(KeyNotFound):
+ log.msg('Will send unencrypted message to %s.' % to_address)
+ signal(proto.SMTP_START_SIGN, self._from_address)
+ d = self._sign(origmsg, from_address)
+ d.addCallback(signal_sign)
+ return d
+ else:
+ return failure
+
+ log.msg("Will encrypt the message with %s and sign with %s."
+ % (to_address, from_address))
+ signal(proto.SMTP_START_ENCRYPT_AND_SIGN,
+ "%s,%s" % (self._from_address, to_address))
+ d = self._encrypt_and_sign(origmsg, to_address, from_address)
+ d.addCallbacks(signal_encrypt_sign, if_key_not_found_send_unencrypted)
+ return d
- def _encrypt_and_sign(self, origmsg, pubkey, signkey):
+ def _encrypt_and_sign(self, origmsg, encrypt_address, sign_address):
"""
Create an RFC 3156 compliang PGP encrypted and signed message using
- C{pubkey} to encrypt and C{signkey} to sign.
+ C{encrypt_address} to encrypt and C{sign_address} to sign.
:param origmsg: The original message
:type origmsg: email.message.Message
- :param pubkey: The public key used to encrypt the message.
- :type pubkey: OpenPGPKey
- :param signkey: The private key used to sign the message.
- :type signkey: OpenPGPKey
- :return: The encrypted and signed message
- :rtype: MultipartEncrypted
+ :param encrypt_address: The address used to encrypt the message.
+ :type encrypt_address: str
+ :param sign_address: The address used to sign the message.
+ :type sign_address: str
+
+ :return: A Deferred with the MultipartEncrypted message
+ :rtype: Deferred
"""
# create new multipart/encrypted message with 'pgp-encrypted' protocol
- newmsg = MultipartEncrypted('application/pgp-encrypted')
- # move (almost) all headers from original message to the new message
- self._fix_headers(origmsg, newmsg, signkey)
- # create 'application/octet-stream' encrypted message
- encmsg = MIMEApplication(
- self._keymanager.encrypt(origmsg.as_string(unixfrom=False), pubkey,
- sign=signkey),
- _subtype='octet-stream', _encoder=lambda x: x)
- encmsg.add_header('content-disposition', 'attachment',
- filename='msg.asc')
- # create meta message
- metamsg = PGPEncrypted()
- metamsg.add_header('Content-Disposition', 'attachment')
- # attach pgp message parts to new message
- newmsg.attach(metamsg)
- newmsg.attach(encmsg)
- return newmsg
-
-
- def _sign(self, origmsg, signkey):
+
+ def encrypt(res):
+ newmsg, origmsg = res
+ d = self._keymanager.encrypt(
+ origmsg.as_string(unixfrom=False),
+ encrypt_address, OpenPGPKey, sign=sign_address)
+ d.addCallback(lambda encstr: (newmsg, encstr))
+ return d
+
+ def create_encrypted_message(res):
+ newmsg, encstr = res
+ encmsg = MIMEApplication(
+ encstr, _subtype='octet-stream', _encoder=lambda x: x)
+ encmsg.add_header('content-disposition', 'attachment',
+ filename='msg.asc')
+ # create meta message
+ metamsg = PGPEncrypted()
+ metamsg.add_header('Content-Disposition', 'attachment')
+ # attach pgp message parts to new message
+ newmsg.attach(metamsg)
+ newmsg.attach(encmsg)
+ return newmsg
+
+ d = self._fix_headers(
+ origmsg,
+ MultipartEncrypted('application/pgp-encrypted'),
+ sign_address)
+ d.addCallback(encrypt)
+ d.addCallback(create_encrypted_message)
+ return d
+
+ def _sign(self, origmsg, sign_address):
"""
- Create an RFC 3156 compliant PGP signed MIME message using C{signkey}.
+ Create an RFC 3156 compliant PGP signed MIME message using
+ C{sign_address}.
:param origmsg: The original message
:type origmsg: email.message.Message
- :param signkey: The private key used to sign the message.
- :type signkey: leap.common.keymanager.openpgp.OpenPGPKey
- :return: The signed message.
- :rtype: MultipartSigned
+ :param sign_address: The address used to sign the message.
+ :type sign_address: str
+
+ :return: A Deferred with the MultipartSigned message.
+ :rtype: Deferred
"""
- # create new multipart/signed message
- newmsg = MultipartSigned('application/pgp-signature', 'pgp-sha512')
- # move (almost) all headers from original message to the new message
- self._fix_headers(origmsg, newmsg, signkey)
# apply base64 content-transfer-encoding
encode_base64_rec(origmsg)
# get message text with headers and replace \n for \r\n
@@ -316,17 +332,27 @@ class OutgoingMail:
if origmsg.is_multipart():
if not msgtext.endswith("\r\n"):
msgtext += "\r\n"
- # calculate signature
- signature = self._keymanager.sign(msgtext, signkey, digest_algo='SHA512',
- clearsign=False, detach=True, binary=False)
- sigmsg = PGPSignature(signature)
- # attach original message and signature to new message
- newmsg.attach(origmsg)
- newmsg.attach(sigmsg)
- return newmsg
+ def create_signed_message(res):
+ (msg, _), signature = res
+ sigmsg = PGPSignature(signature)
+ # attach original message and signature to new message
+ msg.attach(origmsg)
+ msg.attach(sigmsg)
+ return msg
+
+ dh = self._fix_headers(
+ origmsg,
+ MultipartSigned('application/pgp-signature', 'pgp-sha512'),
+ sign_address)
+ ds = self._keymanager.sign(
+ msgtext, sign_address, OpenPGPKey, digest_algo='SHA512',
+ clearsign=False, detach=True, binary=False)
+ d = defer.gatherResults([dh, ds])
+ d.addCallback(create_signed_message)
+ return d
- def _fix_headers(self, origmsg, newmsg, signkey):
+ def _fix_headers(self, origmsg, newmsg, sign_address):
"""
Move some headers from C{origmsg} to C{newmsg}, delete unwanted
headers from C{origmsg} and add new headers to C{newms}.
@@ -360,8 +386,13 @@ class OutgoingMail:
:type origmsg: email.message.Message
:param newmsg: The new message being created.
:type newmsg: email.message.Message
- :param signkey: The key used to sign C{newmsg}
- :type signkey: OpenPGPKey
+ :param sign_address: The address used to sign C{newmsg}
+ :type sign_address: str
+
+ :return: A Deferred with a touple:
+ (new Message with the unencrypted headers,
+ original Message with headers removed)
+ :rtype: Deferred
"""
# move headers from origmsg to newmsg
headers = origmsg.items()
@@ -375,11 +406,17 @@ class OutgoingMail:
del (origmsg[hkey])
# add a new message-id to newmsg
newmsg.add_header('Message-Id', smtp.messageid())
- # add openpgp header to newmsg
- username, domain = signkey.address.split('@')
- newmsg.add_header(
- 'OpenPGP', 'id=%s' % signkey.key_id,
- url='https://%s/key/%s' % (domain, username),
- preference='signencrypt')
# delete user-agent from origmsg
del (origmsg['user-agent'])
+
+ def add_openpgp_header(signkey):
+ username, domain = sign_address.split('@')
+ newmsg.add_header(
+ 'OpenPGP', 'id=%s' % signkey.key_id,
+ url='https://%s/key/%s' % (domain, username),
+ preference='signencrypt')
+ return newmsg, origmsg
+
+ d = self._keymanager.get_key(sign_address, OpenPGPKey, private=True)
+ d.addCallback(add_openpgp_header)
+ return d