From d5e8be49799e511a0e029b90effbdf827a4e8c38 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Wei=C3=9Fschuh?= Date: Mon, 18 Aug 2014 19:11:07 +0000 Subject: [PATCH 1/8] [tests] directly monkeypatch requests Session need for later changes --- tests/storage/test_http.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/storage/test_http.py b/tests/storage/test_http.py index 648f98a..d3d74cf 100644 --- a/tests/storage/test_http.py +++ b/tests/storage/test_http.py @@ -41,7 +41,7 @@ def test_list(monkeypatch): u'\n'.join([u'BEGIN:VCALENDAR'] + items + [u'END:VCALENDAR']) ] * 2 - def get(method, url, *a, **kw): + def get(self, method, url, *a, **kw): assert method == 'GET' assert url == collection_url r = Response() @@ -52,7 +52,7 @@ def test_list(monkeypatch): r.encoding = 'ISO-8859-1' return r - monkeypatch.setattr('requests.request', get) + monkeypatch.setattr('requests.sessions.Session.request', get) s = HttpStorage(url=collection_url) From 56d566e55a2310007eef0d0c20aa814ead73d1a6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Wei=C3=9Fschuh?= Date: Mon, 18 Aug 2014 19:16:13 +0000 Subject: [PATCH 2/8] add a tls_prefix parameter to utils.request This assumes that a backend only ever connects to a single domain, because we set the fingerprint on the whole session --- vdirsyncer/utils/__init__.py | 31 +++++++++++++++++++++++++++---- 1 file changed, 27 insertions(+), 4 deletions(-) diff --git a/vdirsyncer/utils/__init__.py b/vdirsyncer/utils/__init__.py index 937c439..402709a 100644 --- a/vdirsyncer/utils/__init__.py +++ b/vdirsyncer/utils/__init__.py @@ -12,6 +12,7 @@ import os import click import requests +from requests.packages.urllib3.poolmanager import PoolManager from .. import exceptions, log from .compat import iteritems, urlparse @@ -178,8 +179,23 @@ def _password_from_keyring(username, resource): key = new_key +class _FingerprintAdapter(requests.adapters.HTTPAdapter): + def __init__(self, fingerprint=None, **kwargs): + self.fingerprint = str(fingerprint) + super(_FingerprintAdapter, self).__init__(**kwargs) + + def send(self, *args, **kwargs): + return super(_FingerprintAdapter, self).send(*args, **kwargs) + + def init_poolmanager(self, connections, maxsize, block=False): + self.poolmanager = PoolManager(num_pools=connections, + maxsize=maxsize, + block=block, + assert_fingerprint=self.fingerprint) + + def request(method, url, data=None, headers=None, auth=None, verify=None, - session=None, latin1_fallback=True): + session=None, latin1_fallback=True, tls_fingerprint=None): ''' Wrapper method for requests, to ease logging and mocking. Parameters should be the same as for ``requests.request``, except: @@ -194,9 +210,16 @@ def request(method, url, data=None, headers=None, auth=None, verify=None, ''' if session is None: - func = requests.request - else: - func = session.request + session = requests.Session() + + if tls_fingerprint is not None: + https_prefix = 'https://' + + if not isinstance(session.adapters[https_prefix], _FingerprintAdapter): + fingerprint_adapter = _FingerprintAdapter(tls_fingerprint) + session.mount(https_prefix, fingerprint_adapter) + + func = session.request logger.debug(u'{} {}'.format(method, url)) logger.debug(headers) From af06e24f45719ffea80b5bc740728db1fbd98813 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Wei=C3=9Fschuh?= Date: Mon, 18 Aug 2014 19:17:09 +0000 Subject: [PATCH 3/8] enable tls_fingerprint for the dav backend --- vdirsyncer/storage/dav.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/vdirsyncer/storage/dav.py b/vdirsyncer/storage/dav.py index b71add5..66ce2a3 100644 --- a/vdirsyncer/storage/dav.py +++ b/vdirsyncer/storage/dav.py @@ -162,13 +162,14 @@ class DavSession(object): ''' def __init__(self, url, username='', password='', verify=True, auth=None, - useragent=USERAGENT, dav_header=None): + useragent=USERAGENT, tls_fingerprint=None, dav_header=None): if username and not password: password = utils.get_password(username, url) self._settings = { 'verify': prepare_verify(verify), - 'auth': prepare_auth(auth, username, password) + 'auth': prepare_auth(auth, username, password), + 'tls_fingerprint': tls_fingerprint, } self.useragent = useragent self.url = url.rstrip('/') + '/' @@ -248,14 +249,15 @@ class DavStorage(Storage): def __init__(self, url, username='', password='', collection=None, verify=True, auth=None, useragent=USERAGENT, - unsafe_href_chars='@', **kwargs): + unsafe_href_chars='@', tls_fingerprint=None, **kwargs): super(DavStorage, self).__init__(**kwargs) url = url.rstrip('/') + '/' if collection is not None: url = utils.urlparse.urljoin(url, collection) self.session = DavSession(url, username, password, verify, auth, - useragent, dav_header=self.dav_header) + useragent, tls_fingerprint, + dav_header=self.dav_header) self.collection = collection self.unsafe_href_chars = unsafe_href_chars From cd72de610ac46d5fefc7c73f3bd35cfdc65b8361 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Wei=C3=9Fschuh?= Date: Mon, 18 Aug 2014 19:17:27 +0000 Subject: [PATCH 4/8] add tls_fingerprint support to the http backend --- vdirsyncer/storage/http.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/vdirsyncer/storage/http.py b/vdirsyncer/storage/http.py index ad918e6..4bc2757 100644 --- a/vdirsyncer/storage/http.py +++ b/vdirsyncer/storage/http.py @@ -81,7 +81,8 @@ class HttpStorage(Storage): _items = None def __init__(self, url, username='', password='', collection=None, - verify=True, auth=None, useragent=USERAGENT, **kwargs): + verify=True, auth=None, useragent=USERAGENT, + tls_fingerprint=None, **kwargs): super(HttpStorage, self).__init__(**kwargs) if username and not password: @@ -89,6 +90,7 @@ class HttpStorage(Storage): self._settings = { 'verify': prepare_verify(verify), + 'tls_fingerprint': tls_fingerprint, 'auth': prepare_auth(auth, username, password), 'latin1_fallback': False } From 9d034b7ed67a0bddaac997ce9af81fae8cadb481 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Wei=C3=9Fschuh?= Date: Mon, 18 Aug 2014 19:20:25 +0000 Subject: [PATCH 5/8] add myself to AUTHORS.rst --- AUTHORS.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/AUTHORS.rst b/AUTHORS.rst index c4b63ec..89114c3 100644 --- a/AUTHORS.rst +++ b/AUTHORS.rst @@ -7,3 +7,4 @@ In alphabetical order: - Clément Mondon - Julian Mehne - Markus Unterwaditzer +- Thomas Weißschuh From c9cfd0f1edc85af8fc84ab3ed005025a23f387aa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Wei=C3=9Fschuh?= Date: Mon, 18 Aug 2014 19:21:00 +0000 Subject: [PATCH 6/8] s/CONTRIBUTORS.rst/AUTHORS.rst/ --- CONTRIBUTING.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst index 2da2c5b..f5b83f4 100644 --- a/CONTRIBUTING.rst +++ b/CONTRIBUTING.rst @@ -20,6 +20,6 @@ * But not because you wrote too few tests. - * Add yourself to ``CONTRIBUTORS.rst``. Don't add anything to + * Add yourself to ``AUTHORS.rst``. Don't add anything to ``CHANGELOG.rst``, I do that myself shortly before the release. You can help by writing meaningful commit messages. From 0de3102c2c39e68c56d76cdd471bfa63376c32e3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Wei=C3=9Fschuh?= Date: Mon, 18 Aug 2014 21:02:23 +0000 Subject: [PATCH 7/8] add tls_fingerprint to storage.dav.DavStorage --- vdirsyncer/storage/dav.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/vdirsyncer/storage/dav.py b/vdirsyncer/storage/dav.py index 66ce2a3..bb43a57 100644 --- a/vdirsyncer/storage/dav.py +++ b/vdirsyncer/storage/dav.py @@ -270,7 +270,8 @@ class DavStorage(Storage): if kwargs.pop('collection', None) is not None: raise TypeError('collection argument must not be given.') discover_args, _ = utils.split_dict(kwargs, lambda key: key in ( - 'username', 'password', 'verify', 'auth', 'useragent' + 'username', 'password', 'verify', 'auth', 'useragent', + 'tls_fingerprint', )) d = cls.discovery_class(DavSession( url=url, dav_header=None, **discover_args)) From 8c77c57d4c95aa5ec18f6de910ef5bfe27c675c4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Wei=C3=9Fschuh?= Date: Mon, 18 Aug 2014 21:08:11 +0000 Subject: [PATCH 8/8] document tls_fingerprint --- vdirsyncer/storage/dav.py | 2 ++ vdirsyncer/storage/http.py | 2 ++ 2 files changed, 4 insertions(+) diff --git a/vdirsyncer/storage/dav.py b/vdirsyncer/storage/dav.py index bb43a57..169d52c 100644 --- a/vdirsyncer/storage/dav.py +++ b/vdirsyncer/storage/dav.py @@ -223,6 +223,8 @@ class DavStorage(Storage): :param password: Password for authentication. :param verify: Verify SSL certificate, default True. This can also be a local path to a self-signed SSL certificate. + :param tls_fingerprint: Optional. SHA1 or MD5 fingerprint of the + expected server certificate. :param auth: Optional. Either ``basic``, ``digest`` or ``guess``. Default ``guess``. If you know yours, consider setting it explicitly for performance. diff --git a/vdirsyncer/storage/http.py b/vdirsyncer/storage/http.py index 4bc2757..4e0ef11 100644 --- a/vdirsyncer/storage/http.py +++ b/vdirsyncer/storage/http.py @@ -54,6 +54,8 @@ class HttpStorage(Storage): :param password: Password for authentication. :param verify: Verify SSL certificate, default True. This can also be a local path to a self-signed SSL certificate. + :param tls_fingerprint: Optional. SHA1 or MD5 fingerprint of the + expected server certificate. :param auth: Optional. Either ``basic``, ``digest`` or ``guess``. Default ``guess``. If you know yours, consider setting it explicitly for performance.