summaryrefslogtreecommitdiff
path: root/mail
diff options
context:
space:
mode:
Diffstat (limited to 'mail')
-rw-r--r--mail/changes/feature-6617_attach_public_key1
-rw-r--r--mail/src/leap/mail/outgoing/service.py42
-rw-r--r--mail/src/leap/mail/outgoing/tests/test_outgoing.py55
-rw-r--r--mail/src/leap/mail/smtp/gateway.py1
4 files changed, 93 insertions, 6 deletions
diff --git a/mail/changes/feature-6617_attach_public_key b/mail/changes/feature-6617_attach_public_key
new file mode 100644
index 00000000..49b444b2
--- /dev/null
+++ b/mail/changes/feature-6617_attach_public_key
@@ -0,0 +1 @@
+- add public key as attachment (Closes: #6617)
diff --git a/mail/src/leap/mail/outgoing/service.py b/mail/src/leap/mail/outgoing/service.py
index 88b8895e..97771870 100644
--- a/mail/src/leap/mail/outgoing/service.py
+++ b/mail/src/leap/mail/outgoing/service.py
@@ -18,6 +18,8 @@ import re
from StringIO import StringIO
from email.parser import Parser
from email.mime.application import MIMEApplication
+from email.mime.multipart import MIMEMultipart
+from email.mime.text import MIMEText
from OpenSSL import SSL
@@ -250,10 +252,48 @@ class OutgoingMail:
% (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 = self._maybe_attach_key(origmsg, from_address, to_address)
+ d.addCallback(self._encrypt_and_sign, to_address, from_address)
d.addCallbacks(signal_encrypt_sign, if_key_not_found_send_unencrypted)
return d
+ def _maybe_attach_key(self, origmsg, from_address, to_address):
+ filename = "%s-email-key.asc" % (from_address,)
+
+ def attach_if_address_hasnt_encrypted(to_key):
+ # if the sign_used flag is true that means that we got an encrypted
+ # email from this address, because we conly check signatures on
+ # encrypted emails. In this case we don't attach.
+ # XXX: this might not be true some time in the future
+ if to_key.sign_used:
+ return origmsg
+
+ d = self._keymanager.get_key(from_address, OpenPGPKey,
+ fetch_remote=False)
+ d.addCallback(attach_key)
+ return d
+
+ def attach_key(from_key):
+ msg = origmsg
+ if not origmsg.is_multipart():
+ msg = MIMEMultipart()
+ for h, v in origmsg.items():
+ msg.add_header(h, v)
+ msg.attach(MIMEText(origmsg.get_payload()))
+
+ keymsg = MIMEApplication(from_key.key_data, _subtype='pgp-keys',
+ _encoder=lambda x: x)
+ keymsg.add_header('content-disposition', 'attachment',
+ filename=filename)
+ msg.attach(keymsg)
+ return msg
+
+ d = self._keymanager.get_key(to_address, OpenPGPKey,
+ fetch_remote=False)
+ d.addCallback(attach_if_address_hasnt_encrypted)
+ d.addErrback(lambda _: origmsg)
+ return d
+
def _encrypt_and_sign(self, origmsg, encrypt_address, sign_address):
"""
Create an RFC 3156 compliang PGP encrypted and signed message using
diff --git a/mail/src/leap/mail/outgoing/tests/test_outgoing.py b/mail/src/leap/mail/outgoing/tests/test_outgoing.py
index d7423b69..0eb05c87 100644
--- a/mail/src/leap/mail/outgoing/tests/test_outgoing.py
+++ b/mail/src/leap/mail/outgoing/tests/test_outgoing.py
@@ -21,6 +21,7 @@ SMTP gateway tests.
"""
import re
+from email.parser import Parser
from datetime import datetime
from twisted.internet.defer import fail
from twisted.mail.smtp import User
@@ -33,10 +34,14 @@ from leap.mail.tests import (
TestCaseWithKeyManager,
ADDRESS,
ADDRESS_2,
+ PUBLIC_KEY_2,
)
from leap.keymanager import openpgp, errors
+BEGIN_PUBLIC_KEY = "-----BEGIN PGP PUBLIC KEY BLOCK-----"
+
+
class TestOutgoingMail(TestCaseWithKeyManager):
EMAIL_DATA = ['HELO gateway.leap.se',
'MAIL FROM: <%s>' % ADDRESS_2,
@@ -71,7 +76,7 @@ class TestOutgoingMail(TestCaseWithKeyManager):
self._km,
self._config['encrypted_only'],
self.outgoing_mail).buildProtocol(('127.0.0.1', 0))
- self.dest = User(ADDRESS, 'gateway.leap.se', self.proto, ADDRESS)
+ self.dest = User(ADDRESS, 'gateway.leap.se', self.proto, ADDRESS_2)
d = TestCaseWithKeyManager.setUp(self)
d.addCallback(init_outgoing_and_proto)
@@ -88,7 +93,10 @@ class TestOutgoingMail(TestCaseWithKeyManager):
decrypted,
'Decrypted text differs from plaintext.')
- d = self.outgoing_mail._maybe_encrypt_and_sign(self.raw, self.dest)
+ d = self._set_sign_used(ADDRESS)
+ d.addCallback(
+ lambda _:
+ self.outgoing_mail._maybe_encrypt_and_sign(self.raw, self.dest))
d.addCallback(self._assert_encrypted)
d.addCallback(lambda message: self._km.decrypt(
message.get_payload(1).get_payload(), ADDRESS, openpgp.OpenPGPKey))
@@ -109,7 +117,10 @@ class TestOutgoingMail(TestCaseWithKeyManager):
self.assertTrue(ADDRESS_2 in signkey.address,
"Verification failed")
- d = self.outgoing_mail._maybe_encrypt_and_sign(self.raw, self.dest)
+ d = self._set_sign_used(ADDRESS)
+ d.addCallback(
+ lambda _:
+ self.outgoing_mail._maybe_encrypt_and_sign(self.raw, self.dest))
d.addCallback(self._assert_encrypted)
d.addCallback(lambda message: self._km.decrypt(
message.get_payload(1).get_payload(), ADDRESS, openpgp.OpenPGPKey,
@@ -158,7 +169,7 @@ class TestOutgoingMail(TestCaseWithKeyManager):
def assert_verify(key):
self.assertTrue(ADDRESS_2 in key.address,
- 'Signature could not be verified.')
+ 'Signature could not be verified.')
d = self._km.verify(
signed_text, ADDRESS_2, openpgp.OpenPGPKey,
@@ -171,6 +182,42 @@ class TestOutgoingMail(TestCaseWithKeyManager):
d.addCallback(verify)
return d
+ def test_attach_key(self):
+ def check_headers(message):
+ msgstr = message.as_string(unixfrom=False)
+ for header in self.EMAIL_DATA[4:8]:
+ self.assertTrue(header in msgstr,
+ "Missing header: %s" % (header,))
+ return message
+
+ def check_attachment((decrypted, _)):
+ msg = Parser().parsestr(decrypted)
+ for payload in msg.get_payload():
+ if 'application/pgp-keys' == payload.get_content_type():
+ keylines = PUBLIC_KEY_2.split('\n')
+ key = BEGIN_PUBLIC_KEY + '\n\n' + '\n'.join(keylines[4:-1])
+ self.assertTrue(key in payload.get_payload(),
+ "Key attachment don't match")
+ return
+ self.fail("No public key attachment found")
+
+ d = self.outgoing_mail._maybe_encrypt_and_sign(self.raw, self.dest)
+ d.addCallback(self._assert_encrypted)
+ d.addCallback(check_headers)
+ d.addCallback(lambda message: self._km.decrypt(
+ message.get_payload(1).get_payload(), ADDRESS, openpgp.OpenPGPKey))
+ d.addCallback(check_attachment)
+ return d
+
+ def _set_sign_used(self, address):
+ def set_sign(key):
+ key.sign_used = True
+ return self._km.put_key(key, address)
+
+ d = self._km.get_key(address, openpgp.OpenPGPKey, fetch_remote=False)
+ d.addCallback(set_sign)
+ return d
+
def _assert_encrypted(self, res):
message, _ = res
self.assertTrue('Content-Type' in message)
diff --git a/mail/src/leap/mail/smtp/gateway.py b/mail/src/leap/mail/smtp/gateway.py
index 9d78474b..954a7d0f 100644
--- a/mail/src/leap/mail/smtp/gateway.py
+++ b/mail/src/leap/mail/smtp/gateway.py
@@ -309,5 +309,4 @@ class EncryptedMessage(object):
signal(proto.SMTP_CONNECTION_LOST, self._user.dest.addrstr)
# unexpected loss of connection; don't save
-
self._lines = []