From 401165a4b118d7eb2ca08b68bd6945c428d09e21 Mon Sep 17 00:00:00 2001 From: Eygene Ryabinkin Date: Wed, 7 May 2014 01:22:29 +0400 Subject: [PATCH] Extend handling of cert_fingerprint * always verify fingerprint if it was configured; * add ability to specify multiple fingerprints. Signed-off-by: Eygene Ryabinkin --- Changelog.rst | 4 ++++ offlineimap.conf | 7 ++++++- offlineimap/imaplibutil.py | 21 ++++++++++++++------- offlineimap/repository/IMAP.py | 11 ++++++++++- 4 files changed, 34 insertions(+), 9 deletions(-) diff --git a/Changelog.rst b/Changelog.rst index 129709a..5a4c0a3 100644 --- a/Changelog.rst +++ b/Changelog.rst @@ -20,6 +20,10 @@ OfflineIMAP v6.5.6 (YYYY-MM-DD) (if $XDG_CONFIG_HOME/offlineimap/config exists, use it as the default configuration path; ~/.offlineimaprc is still tried after XDG location) (GitHub#32) +* Fix handling of 'cert_fingerprint': verify them even if CA certificate + was configured +* Allow multiple certificate fingerprints to be specified inside + 'cert_fingerprint' OfflineIMAP v6.5.5 (2013-10-07) diff --git a/offlineimap.conf b/offlineimap.conf index 3fc20f7..c7cda28 100644 --- a/offlineimap.conf +++ b/offlineimap.conf @@ -395,8 +395,13 @@ ssl = yes # 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. +# +# Multiple fingerprints can be specified, separated by commas. +# +# Fingerprints must be in hexadecimal form without leading '0x': +# 40 hex digits like bbfe29cf97acb204591edbafe0aa8c8f914287c9. -#cert_fingerprint = +#cert_fingerprint = [, ] # SSL version (optional) # It is best to leave this unset, in which case the correct version will be diff --git a/offlineimap/imaplibutil.py b/offlineimap/imaplibutil.py index f8806dd..2869623 100644 --- a/offlineimap/imaplibutil.py +++ b/offlineimap/imaplibutil.py @@ -141,21 +141,28 @@ class WrappedIMAP4_SSL(UsefulIMAPMixIn, IMAP4_SSL): """Improved version of imaplib.IMAP4_SSL overriding select()""" def __init__(self, *args, **kwargs): self._fingerprint = kwargs.get('fingerprint', None) + if type(self._fingerprint) != type([]): + self._fingerprint = [self._fingerprint] if 'fingerprint' in kwargs: del kwargs['fingerprint'] super(WrappedIMAP4_SSL, self).__init__(*args, **kwargs) def open(self, host=None, port=None): + if not self.ca_certs and not self._fingerprint: + raise OfflineImapError("No CA certificates " + \ + "and no server fingerprints configured. " + \ + "You must configure at least something, otherwise " + \ + "having SSL helps nothing.", OfflineImapError.ERROR.REPO) super(WrappedIMAP4_SSL, self).open(host, port) - if (self._fingerprint or not self.ca_certs): + if self._fingerprint: # compare fingerprints fingerprint = sha1(self.sock.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) + if fingerprint not in self._fingerprint: + raise OfflineImapError("Server SSL fingerprint '%s' " % fingerprint + \ + "for hostname '%s' " % host + \ + "does not match configured fingerprint(s) %s. " % self._fingerprint + \ + "Please verify and set 'cert_fingerprint' accordingly " + \ + "if not set yet.", OfflineImapError.ERROR.REPO) class WrappedIMAP4(UsefulIMAPMixIn, IMAP4): diff --git a/offlineimap/repository/IMAP.py b/offlineimap/repository/IMAP.py index 19db50f..ff1d5e2 100644 --- a/offlineimap/repository/IMAP.py +++ b/offlineimap/repository/IMAP.py @@ -215,7 +215,16 @@ class IMAPRepository(BaseRepository): return self.getconf('ssl_version', None) def get_ssl_fingerprint(self): - return self.getconf('cert_fingerprint', None) + """ + Return array of possible certificate fingerprints. + + Configuration item cert_fingerprint can contain multiple + comma-separated fingerprints in hex form. + + """ + + value = self.getconf('cert_fingerprint', "") + return [f.strip().lower() for f in value.split(',') if f] def getpreauthtunnel(self): return self.getconf('preauthtunnel', None) -- 1.9.0