-- Forwarded message --
From: dave b
Date: 1 December 2010 13:59
Subject: Re: Due offlineimap absence of certificate validation issue
-- Debian BTS#603450
To: John Goerzen
Cc: Jan Lieskovsky , Christoph Höger
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