[PATCH v2] Implement Server SSL fingerprint check

Sebastian Spaeth Sebastian at SSpaeth.de
Tue Aug 30 21:59:08 BST 2011


If we connect to a SSL server (not STARTTLS) and no CA cert has been
specified for verification, we check the configured SSL fingerprint and
bail out in case it has not been set yet, or it does not match.

This means one more mandatory option for SSL configuration, but it
improves security a lot.

Signed-off-by: Sebastian Spaeth <Sebastian at SSpaeth.de>
---
Make Johannes happy. Use sha1 for fingerprinting, name the setting
cert_fingerprint and even check fingerprint if cacertfile has been
configured (if a fingerprint is given in the configuration).

 Changelog.draft.rst            |    4 ++++
 offlineimap/imaplibutil.py     |   22 ++++++++++++++++++++--
 offlineimap/imapserver.py      |    2 ++
 offlineimap/repository/IMAP.py |    3 +++
 4 files changed, 29 insertions(+), 2 deletions(-)

diff --git a/Changelog.draft.rst b/Changelog.draft.rst
index 74b6368..edd0f75 100644
--- a/Changelog.draft.rst
+++ b/Changelog.draft.rst
@@ -17,6 +17,10 @@ New Features
   synchronization, but only skip that message, informing the user at the
   end of the sync run.
  
+* If you connect via ssl and 'cert_fingerprint' is configured, we check
+  that the server certificate is actually known and identical by
+  comparing the stored sha1 fingerprint with the current one.
+
 Changes
 -------
 
diff --git a/offlineimap/imaplibutil.py b/offlineimap/imaplibutil.py
index fa3a303..96d759f 100644
--- a/offlineimap/imaplibutil.py
+++ b/offlineimap/imaplibutil.py
@@ -21,8 +21,10 @@ import re
 import socket
 import time
 import subprocess
-from offlineimap.ui import getglobalui
 import threading
+from hashlib import sha1
+
+from offlineimap.ui import getglobalui
 from offlineimap import OfflineImapError
 from offlineimap.imaplib2 import IMAP4, IMAP4_SSL, zlib, IMAP4_PORT, InternalDate, Mon2num
 
@@ -130,7 +132,23 @@ def new_mesg(self, s, tn=None, secs=None):
 
 class WrappedIMAP4_SSL(UsefulIMAPMixIn, IMAP4_SSL):
     """Improved version of imaplib.IMAP4_SSL overriding select()"""
-    pass
+    def __init__(self, *args, **kwargs):
+        self._fingerprint = kwargs.get('fingerprint', None)
+        if kwargs.has_key('fingerprint'):
+            del kwargs['fingerprint']
+        super(WrappedIMAP4_SSL, self).__init__(*args, **kwargs)
+
+    def open(self, host=None, port=None):
+        super(WrappedIMAP4_SSL, self).open(host, port)
+        if self._fingerprint or not self.ca_certs:
+            # compare fingerprints
+            fingerprint = sha1(self.sslobj.getpeercert(True)).hexdigest()
+            if fingerprint != self._fingerprint:
+                raise OfflineImapError("Server SSL fingerprint '%s' for hostnam"
+                      "e '%s' does not match configured fingerprint. Please ver"
+                      "ify and set 'cert_fingerprint' accordingly if not set ye"
+                      "t." % (fingerprint, host),
+                                       OfflineImapError.ERROR.REPO)
 
 
 class WrappedIMAP4(UsefulIMAPMixIn, IMAP4):
diff --git a/offlineimap/imapserver.py b/offlineimap/imapserver.py
index 3ce751c..523d838 100644
--- a/offlineimap/imapserver.py
+++ b/offlineimap/imapserver.py
@@ -206,6 +206,7 @@ class IMAPServer:
                     success = 1
                 elif self.usessl:
                     self.ui.connecting(self.hostname, self.port)
+                    fingerprint = self.repos.get_ssl_fingerprint()
                     imapobj = imaplibutil.WrappedIMAP4_SSL(self.hostname,
                                                            self.port,
                                                            self.sslclientkey,
@@ -213,6 +214,7 @@ class IMAPServer:
                                                            self.sslcacertfile,
                                                            self.verifycert,
                                                            timeout=socket.getdefaulttimeout(),
+                                                           fingerprint=fingerprint
                                                            )
                 else:
                     self.ui.connecting(self.hostname, self.port)
diff --git a/offlineimap/repository/IMAP.py b/offlineimap/repository/IMAP.py
index 68eb637..76d0870 100644
--- a/offlineimap/repository/IMAP.py
+++ b/offlineimap/repository/IMAP.py
@@ -182,6 +182,9 @@ class IMAPRepository(BaseRepository):
                                 % (self.name, cacertfile))
         return cacertfile
 
+    def get_ssl_fingerprint(self):
+        return self.getconf('cert_fingerprint', None)
+
     def getpreauthtunnel(self):
         return self.getconf('preauthtunnel', None)
 
-- 
1.7.4.1





More information about the OfflineIMAP-project mailing list