Merge pull request #102 from t-8ch/tls_fingerprints

Tls fingerprints

Fix #100
This commit is contained in:
Markus Unterwaditzer 2014-08-18 23:51:23 +02:00
commit 73b8381ab8
6 changed files with 46 additions and 13 deletions

View file

@ -7,3 +7,4 @@ In alphabetical order:
- Clément Mondon - Clément Mondon
- Julian Mehne - Julian Mehne
- Markus Unterwaditzer - Markus Unterwaditzer
- Thomas Weißschuh

View file

@ -20,6 +20,6 @@
* But not because you wrote too few tests. * 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 ``CHANGELOG.rst``, I do that myself shortly before the release. You can
help by writing meaningful commit messages. help by writing meaningful commit messages.

View file

@ -41,7 +41,7 @@ def test_list(monkeypatch):
u'\n'.join([u'BEGIN:VCALENDAR'] + items + [u'END:VCALENDAR']) u'\n'.join([u'BEGIN:VCALENDAR'] + items + [u'END:VCALENDAR'])
] * 2 ] * 2
def get(method, url, *a, **kw): def get(self, method, url, *a, **kw):
assert method == 'GET' assert method == 'GET'
assert url == collection_url assert url == collection_url
r = Response() r = Response()
@ -52,7 +52,7 @@ def test_list(monkeypatch):
r.encoding = 'ISO-8859-1' r.encoding = 'ISO-8859-1'
return r return r
monkeypatch.setattr('requests.request', get) monkeypatch.setattr('requests.sessions.Session.request', get)
s = HttpStorage(url=collection_url) s = HttpStorage(url=collection_url)

View file

@ -162,13 +162,14 @@ class DavSession(object):
''' '''
def __init__(self, url, username='', password='', verify=True, auth=None, 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: if username and not password:
password = utils.get_password(username, url) password = utils.get_password(username, url)
self._settings = { self._settings = {
'verify': prepare_verify(verify), 'verify': prepare_verify(verify),
'auth': prepare_auth(auth, username, password) 'auth': prepare_auth(auth, username, password),
'tls_fingerprint': tls_fingerprint,
} }
self.useragent = useragent self.useragent = useragent
self.url = url.rstrip('/') + '/' self.url = url.rstrip('/') + '/'
@ -222,6 +223,8 @@ class DavStorage(Storage):
:param password: Password for authentication. :param password: Password for authentication.
:param verify: Verify SSL certificate, default True. This can also be a :param verify: Verify SSL certificate, default True. This can also be a
local path to a self-signed SSL certificate. 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 :param auth: Optional. Either ``basic``, ``digest`` or ``guess``. Default
``guess``. If you know yours, consider setting it explicitly for ``guess``. If you know yours, consider setting it explicitly for
performance. performance.
@ -248,14 +251,15 @@ class DavStorage(Storage):
def __init__(self, url, username='', password='', collection=None, def __init__(self, url, username='', password='', collection=None,
verify=True, auth=None, useragent=USERAGENT, verify=True, auth=None, useragent=USERAGENT,
unsafe_href_chars='@', **kwargs): unsafe_href_chars='@', tls_fingerprint=None, **kwargs):
super(DavStorage, self).__init__(**kwargs) super(DavStorage, self).__init__(**kwargs)
url = url.rstrip('/') + '/' url = url.rstrip('/') + '/'
if collection is not None: if collection is not None:
url = utils.urlparse.urljoin(url, collection) url = utils.urlparse.urljoin(url, collection)
self.session = DavSession(url, username, password, verify, auth, 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.collection = collection
self.unsafe_href_chars = unsafe_href_chars self.unsafe_href_chars = unsafe_href_chars
@ -268,7 +272,8 @@ class DavStorage(Storage):
if kwargs.pop('collection', None) is not None: if kwargs.pop('collection', None) is not None:
raise TypeError('collection argument must not be given.') raise TypeError('collection argument must not be given.')
discover_args, _ = utils.split_dict(kwargs, lambda key: key in ( 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( d = cls.discovery_class(DavSession(
url=url, dav_header=cls.dav_header, **discover_args)) url=url, dav_header=cls.dav_header, **discover_args))

View file

@ -54,6 +54,8 @@ class HttpStorage(Storage):
:param password: Password for authentication. :param password: Password for authentication.
:param verify: Verify SSL certificate, default True. This can also be a :param verify: Verify SSL certificate, default True. This can also be a
local path to a self-signed SSL certificate. 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 :param auth: Optional. Either ``basic``, ``digest`` or ``guess``. Default
``guess``. If you know yours, consider setting it explicitly for ``guess``. If you know yours, consider setting it explicitly for
performance. performance.
@ -81,7 +83,8 @@ class HttpStorage(Storage):
_items = None _items = None
def __init__(self, url, username='', password='', collection=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) super(HttpStorage, self).__init__(**kwargs)
if username and not password: if username and not password:
@ -89,6 +92,7 @@ class HttpStorage(Storage):
self._settings = { self._settings = {
'verify': prepare_verify(verify), 'verify': prepare_verify(verify),
'tls_fingerprint': tls_fingerprint,
'auth': prepare_auth(auth, username, password), 'auth': prepare_auth(auth, username, password),
'latin1_fallback': False 'latin1_fallback': False
} }

View file

@ -12,6 +12,7 @@ import os
import click import click
import requests import requests
from requests.packages.urllib3.poolmanager import PoolManager
from .. import exceptions, log from .. import exceptions, log
from .compat import iteritems, urlparse from .compat import iteritems, urlparse
@ -178,8 +179,23 @@ def _password_from_keyring(username, resource):
key = new_key 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, 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 Wrapper method for requests, to ease logging and mocking. Parameters should
be the same as for ``requests.request``, except: 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: if session is None:
func = requests.request session = requests.Session()
else:
func = session.request 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(u'{} {}'.format(method, url))
logger.debug(headers) logger.debug(headers)