From b85118f81f2dbee077b8ca2daca51697e5d56d51 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Tom=C3=A1s=20Touceda?= <chiiph@leap.se>
Date: Fri, 2 Aug 2013 13:25:06 -0300
Subject: Add client certificate authentication

---
 mail/src/leap/mail/smtp/__init__.py  | 20 ++++++-------
 mail/src/leap/mail/smtp/smtprelay.py | 54 +++++++++++++++++++++++-------------
 2 files changed, 44 insertions(+), 30 deletions(-)

(limited to 'mail/src')

diff --git a/mail/src/leap/mail/smtp/__init__.py b/mail/src/leap/mail/smtp/__init__.py
index 78eb4f88..3b4d9d66 100644
--- a/mail/src/leap/mail/smtp/__init__.py
+++ b/mail/src/leap/mail/smtp/__init__.py
@@ -25,8 +25,8 @@ from twisted.internet import reactor
 from leap.mail.smtp.smtprelay import SMTPFactory
 
 
-def setup_smtp_relay(port, keymanager, smtp_host, smtp_port, smtp_username,
-                     smtp_password, encrypted_only):
+def setup_smtp_relay(port, keymanager, smtp_host, smtp_port,
+                     smtp_cert, smtp_key, encrypted_only):
     """
     Setup SMTP relay to run with Twisted.
 
@@ -42,10 +42,10 @@ def setup_smtp_relay(port, keymanager, smtp_host, smtp_port, smtp_username,
     @type smtp_host: str
     @param smtp_port:  The port of the remote SMTP server.
     @type smtp_port: int
-    @param smtp_username: The username used to connect to remote SMTP server.
-    @type smtp_username: str
-    @param smtp_password: The password used to connect to remote SMTP server.
-    @type smtp_password: str
+    @param smtp_cert: The client certificate for authentication.
+    @type smtp_cert: str
+    @param smtp_key: The client key for authentication.
+    @type smtp_key: str
     @param encrypted_only: Whether the SMTP relay should send unencrypted mail
         or not.
     @type encrypted_only: bool
@@ -56,15 +56,15 @@ def setup_smtp_relay(port, keymanager, smtp_host, smtp_port, smtp_username,
     # {
     #     'host': '<host>',
     #     'port': <int>,
-    #     'username': '<username>',
-    #     'password': '<password>',
+    #     'cert': '<cert path>',
+    #     'key': '<key path>',
     #     'encrypted_only': <True/False>
     # }
     config = {
         'host': smtp_host,
         'port': smtp_port,
-        'username': smtp_username,
-        'password': smtp_password,
+        'cert': smtp_cert,
+        'key': smtp_key,
         'encrypted_only': encrypted_only
     }
 
diff --git a/mail/src/leap/mail/smtp/smtprelay.py b/mail/src/leap/mail/smtp/smtprelay.py
index 17388409..e5a56146 100644
--- a/mail/src/leap/mail/smtp/smtprelay.py
+++ b/mail/src/leap/mail/smtp/smtprelay.py
@@ -19,16 +19,12 @@
 LEAP SMTP encrypted relay.
 """
 
-import re
-import os
-import tempfile
-
-
 from zope.interface import implements
 from StringIO import StringIO
+from OpenSSL import SSL
 from twisted.mail import smtp
 from twisted.internet.protocol import ServerFactory
-from twisted.internet import reactor
+from twisted.internet import reactor, ssl
 from twisted.internet import defer
 from twisted.python import log
 from email.Header import Header
@@ -63,8 +59,8 @@ class MalformedConfig(Exception):
 
 HOST_KEY = 'host'
 PORT_KEY = 'port'
-USERNAME_KEY = 'username'
-PASSWORD_KEY = 'password'
+CERT_KEY = 'cert'
+KEY_KEY = 'key'
 ENCRYPTED_ONLY_KEY = 'encrypted_only'
 
 
@@ -89,17 +85,17 @@ def assert_config_structure(config):
     leap_assert_type(config[HOST_KEY], str)
     leap_assert(PORT_KEY in config)
     leap_assert_type(config[PORT_KEY], int)
-    leap_assert(USERNAME_KEY in config)
-    leap_assert_type(config[USERNAME_KEY], str)
-    leap_assert(PASSWORD_KEY in config)
-    leap_assert_type(config[PASSWORD_KEY], str)
+    leap_assert(CERT_KEY in config)
+    leap_assert_type(config[CERT_KEY], str)
+    leap_assert(KEY_KEY in config)
+    leap_assert_type(config[KEY_KEY], str)
     leap_assert(ENCRYPTED_ONLY_KEY in config)
     leap_assert_type(config[ENCRYPTED_ONLY_KEY], bool)
     # assert received params are not empty
     leap_assert(config[HOST_KEY] != '')
     leap_assert(config[PORT_KEY] is not 0)
-    leap_assert(config[USERNAME_KEY] != '')
-    leap_assert(config[PASSWORD_KEY] != '')
+    leap_assert(config[CERT_KEY] != '')
+    leap_assert(config[KEY_KEY] != '')
 
 
 def validate_address(address):
@@ -143,8 +139,8 @@ class SMTPFactory(ServerFactory):
                 {
                     HOST_KEY: '<str>',
                     PORT_KEY: <int>,
-                    USERNAME_KEY: '<str>',
-                    PASSWORD_KEY: '<str>',
+                    CERT_KEY: '<str>',
+                    KEY_KEY: '<str>',
                     ENCRYPTED_ONLY_KEY: <bool>,
                 }
         @type config: dict
@@ -294,6 +290,18 @@ class SMTPDelivery(object):
 # EncryptedMessage
 #
 
+class CtxFactory(ssl.ClientContextFactory):
+    def __init__(self, cert, key):
+        self.cert = cert
+        self.key = key
+
+    def getContext(self):
+        self.method = SSL.TLSv1_METHOD  #SSLv23_METHOD
+        ctx = ssl.ClientContextFactory.getContext(self)
+        ctx.use_certificate_file(self.cert)
+        ctx.use_privatekey_file(self.key)
+        return ctx
+
 class EncryptedMessage(object):
     """
     Receive plaintext from client, encrypt it and send message to a
@@ -405,17 +413,23 @@ class EncryptedMessage(object):
         @rtype: twisted.internet.defer.Deferred
         """
         msg = self._message.as_string(False)
+
+        log.msg("Connecting to SMTP server %s:%s" % (self._config[HOST_KEY],
+                                                     self._config[PORT_KEY]))
+
         d = defer.Deferred()
         factory = smtp.ESMTPSenderFactory(
-            self._config[USERNAME_KEY],
-            self._config[PASSWORD_KEY],
+            "",
+            "",
             self._fromAddress.addrstr,
             self._user.dest.addrstr,
             StringIO(msg),
             d,
-            requireAuthentication=False,  # for now do unauth, see issue #2474
+            contextFactory=CtxFactory(self._config[CERT_KEY],
+                                      self._config[KEY_KEY]),
+            requireAuthentication=False,
         )
-        # TODO: Change this to connectSSL when cert auth is in place in the platform
+
         reactor.connectTCP(
             self._config[HOST_KEY],
             self._config[PORT_KEY],
-- 
cgit v1.2.3