diff --git a/docs/index.rst b/docs/index.rst index a90cf65..3e912d6 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -18,8 +18,9 @@ Table of Contents :maxdepth: 1 tutorial - config + ssl-tutorial keyring + config supported problems vdir diff --git a/docs/ssl-tutorial.rst b/docs/ssl-tutorial.rst new file mode 100644 index 0000000..b2d59c5 --- /dev/null +++ b/docs/ssl-tutorial.rst @@ -0,0 +1,56 @@ +.. _ssl-tutorial: + +============================== +SSL and certificate validation +============================== + +Vdirsyncer uses the requests_ library for all its HTTP and SSL interaction. + +All SSL configuration is done per-storage. Storages that have anything to do +with SSL have two parameters: ``verify`` and ``verify_fingerprint``. + +- The ``verify`` parameter determines whether to verify SSL certificates. + + 1. The default, ``true``, means that certificates will be validated against a + set of trusted CAs. See :ref:`ssl-cas`. + + 2. The value ``false`` will disable both trusted-CA-validation and the + validation of the certificate's expiration date. Unless combined with + ``verify_fingerprint``, **you should not use this value at all, because + it's a security risk**. + + 3. You can also set ``verify`` to a path of the server's certificate in PEM + format, instead of relying on the default root CAs:: + + [storage foo] + type = caldav + ... + verify = "/path/to/cert.pem" + +- The ``verify_fingerprint`` parameter can be used to compare the SSL + fingerprint to a fixed value. The value can be either a SHA1-fingerprint or + an MD5 one:: + + [storage foo] + type = caldav + ... + verify_fingerprint = "94:FD:7A:CB:50:75:A4:69:82:0A:F8:23:DF:07:FC:69:3E:CD:90:CA" + + Using it will effectively set ``verify=False``. + +.. _ssl-cas: + +Trusted CAs +----------- + +As said, vdirsyncer uses the requests_ library for such parts, which, by +default, `uses its own set of trusted CAs +`_. + +However, the actual behavior depends on how you have installed it. Some Linux +distributions, such as Debian, patch their ``python-requests`` package to use +the system certificate CAs. Normally these two stores are similar enough for +you not to care. If the behavior on your system is somehow confusing, your best +bet is explicitly setting the SSL options above. + +.. _requests: www.python-requests.org/ diff --git a/setup.py b/setup.py index 35ad5ae..dc91380 100644 --- a/setup.py +++ b/setup.py @@ -38,10 +38,13 @@ setup( 'console_scripts': ['vdirsyncer = vdirsyncer.cli:main'] }, install_requires=[ + # https://github.com/mitsuhiko/click/issues/200 'click>=3.1', - 'requests>=2.1', + # https://github.com/shazow/urllib3/pull/444 + 'requests>=2.4.1', 'lxml>=3.0', 'icalendar>=3.6', + # https://github.com/sigmavirus24/requests-toolbelt/pull/28 'requests_toolbelt>=0.3.0' ], extras_require={'keyring': ['keyring']} diff --git a/tests/utils/test_main.py b/tests/utils/test_main.py index cf84838..ad3d984 100644 --- a/tests/utils/test_main.py +++ b/tests/utils/test_main.py @@ -208,6 +208,8 @@ def test_get_class_init_args_on_storage(): assert not required +@pytest.mark.skipif(not utils.compat.PY2, + reason='https://github.com/shazow/urllib3/issues/529') def test_request_ssl(httpsserver): sha1 = '94:FD:7A:CB:50:75:A4:69:82:0A:F8:23:DF:07:FC:69:3E:CD:90:CA' md5 = '19:90:F7:23:94:F2:EF:AB:2B:64:2D:57:3D:25:95:2D' @@ -218,6 +220,10 @@ def test_request_ssl(httpsserver): utils.request('GET', httpsserver.url) assert 'certificate verify failed' in str(excinfo.value) utils.request('GET', httpsserver.url, verify=False) - utils.request('GET', httpsserver.url, verify=False, + utils.request('GET', httpsserver.url, verify_fingerprint=sha1) - utils.request('GET', httpsserver.url, verify=False, verify_fingerprint=md5) + utils.request('GET', httpsserver.url, verify_fingerprint=md5) + with pytest.raises(requests.exceptions.SSLError) as excinfo: + utils.request('GET', httpsserver.url, + verify_fingerprint=''.join(reversed(sha1))) + assert 'Fingerprints did not match' in str(excinfo.value) diff --git a/vdirsyncer/storage/dav.py b/vdirsyncer/storage/dav.py index 23766c3..69c9092 100644 --- a/vdirsyncer/storage/dav.py +++ b/vdirsyncer/storage/dav.py @@ -14,7 +14,8 @@ from requests import session as requests_session from requests.exceptions import HTTPError from .base import Item, Storage -from .http import USERAGENT, prepare_auth, prepare_verify +from .http import HTTP_STORAGE_PARAMETERS, USERAGENT, prepare_auth, \ + prepare_verify from .. import exceptions, log, utils @@ -250,18 +251,9 @@ class DavSession(object): class DavStorage(Storage): - ''' + __doc__ = ''' :param url: Base URL or an URL to a collection. - :param username: Username for authentication. - :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 verify_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. - :param useragent: Default ``vdirsyncer``. + ''' + HTTP_STORAGE_PARAMETERS + ''' :param unsafe_href_chars: Replace the given characters when generating hrefs. Defaults to ``'@'``. diff --git a/vdirsyncer/storage/http.py b/vdirsyncer/storage/http.py index 190c1e8..d3e7498 100644 --- a/vdirsyncer/storage/http.py +++ b/vdirsyncer/storage/http.py @@ -44,21 +44,28 @@ def prepare_verify(verify): return verify -class HttpStorage(Storage): - ''' - Use a simple ``.ics`` file (or similar) from the web. - - :param url: URL to the ``.ics`` file. +HTTP_STORAGE_PARAMETERS = ''' :param username: Username for authentication. :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. + local path to a self-signed SSL certificate. See :ref:`ssl-tutorial` + for more information. :param verify_fingerprint: Optional. SHA1 or MD5 fingerprint of the - expected server certificate. + expected server certificate. See :ref:`ssl-tutorial` for more + information. :param auth: Optional. Either ``basic``, ``digest`` or ``guess``. Default ``guess``. If you know yours, consider setting it explicitly for performance. :param useragent: Default ``vdirsyncer``. +''' + + +class HttpStorage(Storage): + __doc__ = ''' + Use a simple ``.ics`` file (or similar) from the web. + + :param url: URL to the ``.ics`` file. + ''' + HTTP_STORAGE_PARAMETERS + ''' A simple example:: diff --git a/vdirsyncer/utils/__init__.py b/vdirsyncer/utils/__init__.py index 90f5f8d..18c1410 100644 --- a/vdirsyncer/utils/__init__.py +++ b/vdirsyncer/utils/__init__.py @@ -205,6 +205,7 @@ def request(method, url, session=None, latin1_fallback=True, session = requests.Session() if verify_fingerprint is not None: + kwargs['verify'] = False https_prefix = 'https://' if not isinstance(session.adapters[https_prefix], _FingerprintAdapter):