[Date Prev][Date Next] [Thread Prev][Thread Next] [Date Index] [Thread Index]

Bug#603450: Fwd: Due offlineimap absence of certificate validation issue -- Debian BTS#603450



---------- Forwarded message ----------
From: dave b <db.pub.mail@gmail.com>
Date: 1 December 2010 13:59
Subject: Re: Due offlineimap absence of certificate validation issue
-- Debian BTS#603450
To: John Goerzen <jgoerzen@complete.org>
Cc: Jan Lieskovsky <jlieskov@redhat.com>, Christoph Höger
<choeger@cs.tu-berlin.de>


Here have a patch!
This obviously will break connecting to hosts which use a self-signed
certificate.
Perhaps some one else can fix this when they want it fixed ;) ?
I tested using the following config:

# Sample minimal config file.  Copy this to ~/.offlineimaprc and edit to
# suit to get started fast.

[general]
accounts = Test

[Account Test]
localrepository = Local
remoterepository = Remote

[Repository Local]
type = Maildir
localfolders = ~/Test

[Repository Remote]
type = IMAP
ssl = yes
remotehost = imap.gmail.com # this should work
#remotehost = 74.125.39.109 # this should fail
#remotehost = $putselfsignedserveraddresshere #this should fail
remoteuser = jgoerzen

For these hosts (listed here) the expected outcome matched the actual
outcome when I tried offlineimap.
diff --git a/offlineimap/imaplibutil.py b/offlineimap/imaplibutil.py
index a60242b..3df92c2 100644
--- a/offlineimap/imaplibutil.py
+++ b/offlineimap/imaplibutil.py
@@ -27,6 +27,7 @@ try:
     import ssl
     ssl_wrap = ssl.wrap_socket
 except ImportError:
+    print e
     ssl_wrap = socket.ssl
 
 class IMAP4_Tunnel(IMAP4):
@@ -62,7 +63,7 @@ class IMAP4_Tunnel(IMAP4):
         self.infd.close()
         self.outfd.close()
         self.process.wait()
-        
+
 class sslwrapper:
     def __init__(self, sslsock):
         self.sslsock = sslsock
@@ -144,6 +145,27 @@ def new_open(self, host = '', port = IMAP4_PORT):
             raise socket.error(last_error)
         self.file = self.sock.makefile('rb')
 
+
+def _verifycert(cert, hostname):
+    '''Verify that cert (in socket.getpeercert() format) matches hostname.
+    CRLs and subjectAltName are not handled.
+
+    Returns error message if any problems are found and None on success.
+    '''
+    if not cert:
+        return ('no certificate received')
+    dnsname = hostname.lower()
+    for s in cert.get('subject', []):
+        key, value = s[0]
+        if key == 'commonName':
+            certname = value.lower()
+            if (certname == dnsname or
+                '.' in dnsname and certname == '*.' + dnsname.split('.', 1)[1]):
+                return None
+            return ('certificate is for %s') % certname
+    return ('no commonName found in certificate')
+
+
 def new_open_ssl(self, host = '', port = IMAP4_SSL_PORT):
         """Setup connection to remote server on "host:port".
             (default: localhost:standard IMAP4 SSL port).
@@ -171,7 +193,10 @@ def new_open_ssl(self, host = '', port = IMAP4_SSL_PORT):
         if last_error != 0:
             # FIXME
             raise socket.error(last_error)
-        self.sslobj = ssl_wrap(self.sock, self.keyfile, self.certfile)
+        self.sslobj = ssl_wrap(self.sock, self.keyfile, self.certfile, cert_reqs=ssl.CERT_REQUIRED, ca_certs="/etc/ssl/certs/ca-certificates.crt")
+        msg = _verifycert(self.sslobj.getpeercert(), host)
+        if msg:
+            raise socket.error(('%s certificate error: %s') % (host, msg) )
         self.sslobj = sslwrapper(self.sslobj)
 
 mustquote = re.compile(r"[^\w!#$%&'+,.:;<=>?^`|~-]")
diff --git a/offlineimap/imapserver.py b/offlineimap/imapserver.py
index 74f1a27..a4925b1 100644
--- a/offlineimap/imapserver.py
+++ b/offlineimap/imapserver.py
@@ -85,7 +85,7 @@ class UsefulIMAP4_SSL(UsefulIMAPMixIn, imaplibutil.WrappedIMAP4_SSL):
         imaplibutil.new_open_ssl(self, host, port)
 
     # This is the same hack as above, to be used in the case of an SSL
-    # connexion.
+    # connection.
 
     def read(self, size):
         if (system() == 'Darwin') and (size>0) :
@@ -191,7 +191,7 @@ class IMAPServer:
         try:
             if self.gss_step == self.GSS_STATE_STEP:
                 if not self.gss_vc:
-                    rc, self.gss_vc = kerberos.authGSSClientInit('imap@' + 
+                    rc, self.gss_vc = kerberos.authGSSClientInit('imap@' +
                                                                  self.hostname)
                     response = kerberos.authGSSClientResponse(self.gss_vc)
                 rc = kerberos.authGSSClientStep(self.gss_vc, data)
@@ -243,7 +243,7 @@ class IMAPServer:
             self.lastowner[imapobj] = thread.get_ident()
             self.connectionlock.release()
             return imapobj
-        
+
         self.connectionlock.release()   # Release until need to modify data
 
         """ Must be careful here that if we fail we should bail out gracefully
@@ -328,7 +328,7 @@ class IMAPServer:
             if(self.connectionlock.locked()):
                 self.connectionlock.release()
             raise
-    
+
     def connectionwait(self):
         """Waits until there is a connection available.  Note that between
         the time that a connection becomes available and the time it is
@@ -378,7 +378,7 @@ class IMAPServer:
             ui.debug('imap', 'keepalive: connectionlock released')
             threads = []
             imapobjs = []
-        
+
             for i in range(numconnections):
                 ui.debug('imap', 'keepalive: processing connection %d of %d' % (i, numconnections))
                 imapobj = self.acquireconnection()
@@ -424,7 +424,7 @@ class ConfigedIMAPServer(IMAPServer):
         reference = self.repos.getreference()
         server = None
         password = None
-        
+
         if repository.getname() in passwordhash:
             password = passwordhash[repository.getname()]
 

Reply to: