Merge pull request #161 from untitaker/verify_docs

Document certificate verification properly
This commit is contained in:
Markus Unterwaditzer 2015-01-10 23:44:10 +01:00
commit 4384823cd1
7 changed files with 89 additions and 23 deletions

View file

@ -18,8 +18,9 @@ Table of Contents
:maxdepth: 1
tutorial
config
ssl-tutorial
keyring
config
supported
problems
vdir

56
docs/ssl-tutorial.rst Normal file
View file

@ -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
<http://www.python-requests.org/en/latest/user/advanced/#ca-certificates>`_.
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/

View file

@ -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']}

View file

@ -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)

View file

@ -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 ``'@'``.

View file

@ -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::

View file

@ -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):