Make OS-default CA certificate file to be requested explicitely
This simplifies logics for the user, especially if he uses both fingerprint and certificate validation: it is hard to maintain the compatibility with the prior behaviour and to avoid getting default CA bundle to be disabled when fingerprint verification is requested. See http://thread.gmane.org/gmane.mail.imap.offlineimap.general/6695 for discussion about this change. Default CA bundle is requested via 'sslcertfile = OS-DEFAULT'. I had also enforced all cases where explicitely-requested CA bundles are non-existent to be hard errors: when users asks us to use CA bundle (and, thus, certificate validation), but we can't find one, we must error out rather than happily continue and downgrade to no validation. Reported-By: Edd Barrett <edd@theunixzoo.co.uk> Reviewed-By: Nicolas Sebrecht <nicolas.s-dev@laposte.net> Signed-off-by: Eygene Ryabinkin <rea@codelabs.ru>
This commit is contained in:
parent
9b911faa58
commit
f69613965f
@ -497,6 +497,17 @@ remotehost = examplehost
|
|||||||
#
|
#
|
||||||
# Tilde and environment variable expansions will be performed.
|
# Tilde and environment variable expansions will be performed.
|
||||||
#
|
#
|
||||||
|
# Special value OS-DEFAULT makes OfflineIMAP to automatically
|
||||||
|
# determine system-wide location of standard trusted CA roots file
|
||||||
|
# for known OS distributions and use the first bundle encountered
|
||||||
|
# (if any). If no system-wide CA bundle is found, OfflineIMAP
|
||||||
|
# will refuse to continue; this is done to prevent creation
|
||||||
|
# of false security expectations ("I had configured CA bundle,
|
||||||
|
# thou certificate verification shalt be present").
|
||||||
|
#
|
||||||
|
# You can also use fingerprint verification via cert_fingerprint.
|
||||||
|
# See below for more verbose explanation.
|
||||||
|
#
|
||||||
#sslcacertfile = /path/to/cacertfile.crt
|
#sslcacertfile = /path/to/cacertfile.crt
|
||||||
|
|
||||||
|
|
||||||
@ -506,10 +517,13 @@ remotehost = examplehost
|
|||||||
# specified, OfflineIMAP will refuse to sync as it connects to a server
|
# specified, OfflineIMAP will refuse to sync as it connects to a server
|
||||||
# with an unknown "fingerprint". If you are sure you connect to the
|
# with an unknown "fingerprint". If you are sure you connect to the
|
||||||
# correct server, you can then configure the presented server
|
# correct server, you can then configure the presented server
|
||||||
# fingerprint here. OfflineImap will verify that the server fingerprint
|
# fingerprint here. OfflineIMAP will verify that the server fingerprint
|
||||||
# has not changed on each connection and refuse to connect otherwise.
|
# has not changed on each connect and refuse to connect otherwise.
|
||||||
# You can also configure this in addition to CA certificate validation
|
#
|
||||||
# above and it will check both ways.
|
# You can also configure fingerprint validation in addition to
|
||||||
|
# CA certificate validation above and it will check both:
|
||||||
|
# OfflineIMAP fill verify certificate first and if things will be fine,
|
||||||
|
# fingerprint will be validated.
|
||||||
#
|
#
|
||||||
# Multiple fingerprints can be specified, separated by commas.
|
# Multiple fingerprints can be specified, separated by commas.
|
||||||
#
|
#
|
||||||
|
@ -81,9 +81,10 @@ class IMAPServer:
|
|||||||
self.sslclientcert = repos.getsslclientcert()
|
self.sslclientcert = repos.getsslclientcert()
|
||||||
self.sslclientkey = repos.getsslclientkey()
|
self.sslclientkey = repos.getsslclientkey()
|
||||||
self.sslcacertfile = repos.getsslcacertfile()
|
self.sslcacertfile = repos.getsslcacertfile()
|
||||||
self.sslversion = repos.getsslversion()
|
|
||||||
if self.sslcacertfile is None:
|
if self.sslcacertfile is None:
|
||||||
self.__verifycert = None # disable cert verification
|
self.__verifycert = None # disable cert verification
|
||||||
|
self.fingerprint = repos.get_ssl_fingerprint()
|
||||||
|
self.sslversion = repos.getsslversion()
|
||||||
|
|
||||||
self.delim = None
|
self.delim = None
|
||||||
self.root = None
|
self.root = None
|
||||||
@ -394,7 +395,6 @@ class IMAPServer:
|
|||||||
success = 1
|
success = 1
|
||||||
elif self.usessl:
|
elif self.usessl:
|
||||||
self.ui.connecting(self.hostname, self.port)
|
self.ui.connecting(self.hostname, self.port)
|
||||||
fingerprint = self.repos.get_ssl_fingerprint()
|
|
||||||
imapobj = imaplibutil.WrappedIMAP4_SSL(self.hostname,
|
imapobj = imaplibutil.WrappedIMAP4_SSL(self.hostname,
|
||||||
self.port,
|
self.port,
|
||||||
self.sslclientkey,
|
self.sslclientkey,
|
||||||
@ -403,7 +403,7 @@ class IMAPServer:
|
|||||||
self.__verifycert,
|
self.__verifycert,
|
||||||
self.sslversion,
|
self.sslversion,
|
||||||
timeout=socket.getdefaulttimeout(),
|
timeout=socket.getdefaulttimeout(),
|
||||||
fingerprint=fingerprint
|
fingerprint=self.fingerprint
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
self.ui.connecting(self.hostname, self.port)
|
self.ui.connecting(self.hostname, self.port)
|
||||||
@ -468,7 +468,7 @@ class IMAPServer:
|
|||||||
(self.hostname, self.repos)
|
(self.hostname, self.repos)
|
||||||
raise OfflineImapError(reason, severity), None, exc_info()[2]
|
raise OfflineImapError(reason, severity), None, exc_info()[2]
|
||||||
|
|
||||||
elif isinstance(e, SSLError) and e.errno == 1:
|
elif isinstance(e, SSLError) and e.errno == errno.EPERM:
|
||||||
# SSL unknown protocol error
|
# SSL unknown protocol error
|
||||||
# happens e.g. when connecting via SSL to a non-SSL service
|
# happens e.g. when connecting via SSL to a non-SSL service
|
||||||
if self.port != 993:
|
if self.port != 993:
|
||||||
|
@ -25,7 +25,7 @@ from offlineimap.repository.Base import BaseRepository
|
|||||||
from offlineimap import folder, imaputil, imapserver, OfflineImapError
|
from offlineimap import folder, imaputil, imapserver, OfflineImapError
|
||||||
from offlineimap.folder.UIDMaps import MappedIMAPFolder
|
from offlineimap.folder.UIDMaps import MappedIMAPFolder
|
||||||
from offlineimap.threadutil import ExitNotifyThread
|
from offlineimap.threadutil import ExitNotifyThread
|
||||||
from offlineimap.utils.distro import get_os_sslcertfile
|
from offlineimap.utils.distro import get_os_sslcertfile, get_os_sslcertfile_searchpath
|
||||||
|
|
||||||
|
|
||||||
class IMAPRepository(BaseRepository):
|
class IMAPRepository(BaseRepository):
|
||||||
@ -201,16 +201,44 @@ class IMAPRepository(BaseRepository):
|
|||||||
return self.getconf_xform('sslclientkey', xforms, None)
|
return self.getconf_xform('sslclientkey', xforms, None)
|
||||||
|
|
||||||
def getsslcacertfile(self):
|
def getsslcacertfile(self):
|
||||||
"""Return the absolute path of the CA certfile to use, if any"""
|
"""Determines CA bundle.
|
||||||
|
|
||||||
|
Returns path to the CA bundle. It is either explicitely specified
|
||||||
|
or requested via "OS-DEFAULT" value (and we will search known
|
||||||
|
locations for the current OS and distribution).
|
||||||
|
|
||||||
|
If search via "OS-DEFAULT" route yields nothing, we will throw an
|
||||||
|
exception to make our callers distinguish between not specified
|
||||||
|
value and non-existent default CA bundle.
|
||||||
|
|
||||||
|
It is also an error to specify non-existent file via configuration:
|
||||||
|
it will error out later, but, perhaps, with less verbose explanation,
|
||||||
|
so we will also throw an exception. It is consistent with
|
||||||
|
the above behaviour, so any explicitely-requested configuration
|
||||||
|
that doesn't result in an existing file will give an exception.
|
||||||
|
"""
|
||||||
|
|
||||||
xforms = [os.path.expanduser, os.path.expandvars, os.path.abspath]
|
xforms = [os.path.expanduser, os.path.expandvars, os.path.abspath]
|
||||||
cacertfile = self.getconf_xform('sslcacertfile', xforms,
|
cacertfile = self.getconf_xform('sslcacertfile', xforms, None)
|
||||||
get_os_sslcertfile())
|
if self.getconf('sslcacertfile', None) == "OS-DEFAULT":
|
||||||
|
cacertfile = get_os_sslcertfile()
|
||||||
|
if cacertfile == None:
|
||||||
|
searchpath = get_os_sslcertfile_searchpath()
|
||||||
|
if searchpath:
|
||||||
|
reason = "Default CA bundle was requested, "\
|
||||||
|
"but no existing locations available. "\
|
||||||
|
"Tried %s." % (", ".join(searchpath))
|
||||||
|
else:
|
||||||
|
reason = "Default CA bundle was requested, "\
|
||||||
|
"but OfflineIMAP doesn't know any for your "\
|
||||||
|
"current operating system."
|
||||||
|
raise OfflineImapError(reason, OfflineImapError.ERROR.REPO)
|
||||||
if cacertfile is None:
|
if cacertfile is None:
|
||||||
return None
|
return None
|
||||||
if not os.path.isfile(cacertfile):
|
if not os.path.isfile(cacertfile):
|
||||||
raise SyntaxWarning("CA certfile for repository '%s' could "
|
reason = "CA certfile for repository '%s' couldn't be found. "\
|
||||||
"not be found. No such file: '%s'" \
|
"No such file: '%s'" % (self.name, cacertfile)
|
||||||
% (self.name, cacertfile))
|
raise OfflineImapError(reason, OfflineImapError.ERROR.REPO)
|
||||||
return cacertfile
|
return cacertfile
|
||||||
|
|
||||||
def getsslversion(self):
|
def getsslversion(self):
|
||||||
|
@ -14,7 +14,6 @@ import os
|
|||||||
__DEF_OS_LOCATIONS = {
|
__DEF_OS_LOCATIONS = {
|
||||||
'freebsd': '/usr/local/share/certs/ca-root-nss.crt',
|
'freebsd': '/usr/local/share/certs/ca-root-nss.crt',
|
||||||
'openbsd': '/etc/ssl/cert.pem',
|
'openbsd': '/etc/ssl/cert.pem',
|
||||||
'netbsd': None,
|
|
||||||
'dragonfly': '/etc/ssl/cert.pem',
|
'dragonfly': '/etc/ssl/cert.pem',
|
||||||
'darwin': [
|
'darwin': [
|
||||||
# MacPorts, port curl-ca-bundle
|
# MacPorts, port curl-ca-bundle
|
||||||
@ -48,6 +47,26 @@ def get_os_name():
|
|||||||
|
|
||||||
return OS
|
return OS
|
||||||
|
|
||||||
|
def get_os_sslcertfile_searchpath():
|
||||||
|
"""Returns search path for CA bundle for the current OS.
|
||||||
|
|
||||||
|
We will return an iterable even if configuration has just
|
||||||
|
a single value: it is easier for our callers to be sure
|
||||||
|
that they can iterate over result.
|
||||||
|
|
||||||
|
Returned value of None means that there is no search path
|
||||||
|
at all.
|
||||||
|
"""
|
||||||
|
|
||||||
|
OS = get_os_name()
|
||||||
|
|
||||||
|
l = None
|
||||||
|
if OS in __DEF_OS_LOCATIONS:
|
||||||
|
l = __DEF_OS_LOCATIONS[OS]
|
||||||
|
if not hasattr(l, '__iter__'):
|
||||||
|
l = (l, )
|
||||||
|
return l
|
||||||
|
|
||||||
|
|
||||||
def get_os_sslcertfile():
|
def get_os_sslcertfile():
|
||||||
"""
|
"""
|
||||||
@ -57,18 +76,16 @@ def get_os_sslcertfile():
|
|||||||
Returns the location of the file or None if there is
|
Returns the location of the file or None if there is
|
||||||
no known CA certificate file or all known locations
|
no known CA certificate file or all known locations
|
||||||
correspond to non-existing filesystem objects.
|
correspond to non-existing filesystem objects.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
OS = get_os_name()
|
|
||||||
|
|
||||||
if OS in __DEF_OS_LOCATIONS:
|
l = get_os_sslcertfile_searchpath()
|
||||||
l = __DEF_OS_LOCATIONS[OS]
|
if l == None:
|
||||||
if not hasattr(l, '__iter__'):
|
return None
|
||||||
l = (l, )
|
|
||||||
for f in l:
|
for f in l:
|
||||||
assert (type(f) == type(""))
|
assert (type(f) == type(""))
|
||||||
if os.path.exists(f) and \
|
if os.path.exists(f) and \
|
||||||
(os.path.isfile(f) or os.path.islink(f)):
|
(os.path.isfile(f) or os.path.islink(f)):
|
||||||
return f
|
return f
|
||||||
|
|
||||||
return None
|
return None
|
||||||
|
Loading…
Reference in New Issue
Block a user