mirror of
https://github.com/samsonjs/vdirsyncer.git
synced 2026-04-27 14:57:41 +00:00
Clean up some invalid TLS configuration branches
This commit is contained in:
parent
60352f84fe
commit
2fbb0ab7a5
8 changed files with 88 additions and 86 deletions
|
|
@ -26,8 +26,6 @@ Version 0.19.0
|
||||||
- A new ``asyncio`` backend is now used. So far, this shows substantial speed
|
- A new ``asyncio`` backend is now used. So far, this shows substantial speed
|
||||||
improvements in ``discovery`` and ``metasync``, but little change in `sync`.
|
improvements in ``discovery`` and ``metasync``, but little change in `sync`.
|
||||||
This will likely continue improving over time. :gh:`906`
|
This will likely continue improving over time. :gh:`906`
|
||||||
- Support for `md5` and `sha1` certificate fingerprints has been dropped. If
|
|
||||||
you're validating certificate fingerprints, use `sha256` instead.
|
|
||||||
- The ``google`` storage types no longer require ``requests-oauthlib``, but
|
- The ``google`` storage types no longer require ``requests-oauthlib``, but
|
||||||
require ``python-aiohttp-oauthlib`` instead.
|
require ``python-aiohttp-oauthlib`` instead.
|
||||||
- Vdirsyncer no longer includes experimental support for `EteSync
|
- Vdirsyncer no longer includes experimental support for `EteSync
|
||||||
|
|
@ -41,6 +39,22 @@ Version 0.19.0
|
||||||
|
|
||||||
.. _etesync-dav: https://github.com/etesync/etesync-dav
|
.. _etesync-dav: https://github.com/etesync/etesync-dav
|
||||||
|
|
||||||
|
Changes to SSL configuration
|
||||||
|
----------------------------
|
||||||
|
|
||||||
|
Support for `md5` and `sha1` certificate fingerprints has been dropped. If
|
||||||
|
you're validating certificate fingerprints, use `sha256` instead.
|
||||||
|
|
||||||
|
# XXX: just make it one arg
|
||||||
|
|
||||||
|
When using a custom `verify_fingerprint`, CA validation is always disabled.
|
||||||
|
|
||||||
|
If `verify_fingerprint` is unset, CA verification is always active. Disabling
|
||||||
|
both features is insecure and no longer supported.
|
||||||
|
|
||||||
|
The `verify` parameter no longer takes boolean values, it is now optional and
|
||||||
|
only takes a string to a custom CA for verification.
|
||||||
|
|
||||||
Version 0.18.0
|
Version 0.18.0
|
||||||
==============
|
==============
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -175,7 +175,7 @@ CalDAV and CardDAV
|
||||||
url = "..."
|
url = "..."
|
||||||
#username = ""
|
#username = ""
|
||||||
#password = ""
|
#password = ""
|
||||||
#verify = true
|
#verify = /path/to/custom_ca.pem
|
||||||
#auth = null
|
#auth = null
|
||||||
#useragent = "vdirsyncer/0.16.4"
|
#useragent = "vdirsyncer/0.16.4"
|
||||||
#verify_fingerprint = null
|
#verify_fingerprint = null
|
||||||
|
|
@ -208,12 +208,10 @@ CalDAV and CardDAV
|
||||||
:param url: Base URL or an URL to a calendar.
|
:param url: Base URL or an URL to a calendar.
|
||||||
:param username: Username for authentication.
|
:param username: Username for authentication.
|
||||||
:param password: Password for authentication.
|
:param password: Password for authentication.
|
||||||
:param verify: Verify SSL certificate, default True. This can also be a
|
:param verify: Optional. Local path to a self-signed SSL certificate.
|
||||||
local path to a self-signed SSL certificate. See :ref:`ssl-tutorial`
|
See :ref:`ssl-tutorial` for more information.
|
||||||
for more information.
|
:param verify_fingerprint: Optional. SHA256 fingerprint of the expected
|
||||||
:param verify_fingerprint: Optional. SHA1 or MD5 fingerprint of the
|
server certificate. See :ref:`ssl-tutorial` for more information.
|
||||||
expected server certificate. See :ref:`ssl-tutorial` for more
|
|
||||||
information.
|
|
||||||
:param auth: Optional. Either ``basic``, ``digest`` or ``guess``. The
|
:param auth: Optional. Either ``basic``, ``digest`` or ``guess``. The
|
||||||
default is preemptive Basic auth, sending credentials even if server
|
default is preemptive Basic auth, sending credentials even if server
|
||||||
didn't request them. This saves from an additional roundtrip per
|
didn't request them. This saves from an additional roundtrip per
|
||||||
|
|
@ -235,7 +233,7 @@ CalDAV and CardDAV
|
||||||
url = "..."
|
url = "..."
|
||||||
#username = ""
|
#username = ""
|
||||||
#password = ""
|
#password = ""
|
||||||
#verify = true
|
#verify = /path/to/custom_ca.pem
|
||||||
#auth = null
|
#auth = null
|
||||||
#useragent = "vdirsyncer/0.16.4"
|
#useragent = "vdirsyncer/0.16.4"
|
||||||
#verify_fingerprint = null
|
#verify_fingerprint = null
|
||||||
|
|
@ -244,12 +242,10 @@ CalDAV and CardDAV
|
||||||
:param url: Base URL or an URL to an addressbook.
|
:param url: Base URL or an URL to an addressbook.
|
||||||
:param username: Username for authentication.
|
:param username: Username for authentication.
|
||||||
:param password: Password for authentication.
|
:param password: Password for authentication.
|
||||||
:param verify: Verify SSL certificate, default True. This can also be a
|
:param verify: Optional. Local path to a self-signed SSL certificate.
|
||||||
local path to a self-signed SSL certificate. See
|
See :ref:`ssl-tutorial` for more information.
|
||||||
:ref:`ssl-tutorial` for more information.
|
:param verify_fingerprint: Optional. SHA256 fingerprint of the expected
|
||||||
:param verify_fingerprint: Optional. SHA1 or MD5 fingerprint of the expected
|
server certificate. See :ref:`ssl-tutorial` for more information.
|
||||||
server certificate. See :ref:`ssl-tutorial` for
|
|
||||||
more information.
|
|
||||||
:param auth: Optional. Either ``basic``, ``digest`` or ``guess``. The
|
:param auth: Optional. Either ``basic``, ``digest`` or ``guess``. The
|
||||||
default is preemptive Basic auth, sending credentials even if
|
default is preemptive Basic auth, sending credentials even if
|
||||||
server didn't request them. This saves from an additional
|
server didn't request them. This saves from an additional
|
||||||
|
|
@ -478,12 +474,10 @@ leads to an error.
|
||||||
:param url: URL to the ``.ics`` file.
|
:param url: URL to the ``.ics`` file.
|
||||||
:param username: Username for authentication.
|
:param username: Username for authentication.
|
||||||
:param password: Password for authentication.
|
:param password: Password for authentication.
|
||||||
:param verify: Verify SSL certificate, default True. This can also be a
|
:param verify: Optional. Local path to a self-signed SSL certificate.
|
||||||
local path to a self-signed SSL certificate. See :ref:`ssl-tutorial`
|
See :ref:`ssl-tutorial` for more information.
|
||||||
for more information.
|
:param verify_fingerprint: Optional. SHA256 fingerprint of the expected
|
||||||
:param verify_fingerprint: Optional. SHA1 or MD5 fingerprint of the
|
server certificate. See :ref:`ssl-tutorial` for more information.
|
||||||
expected server certificate. See :ref:`ssl-tutorial` for more
|
|
||||||
information.
|
|
||||||
:param auth: Optional. Either ``basic``, ``digest`` or ``guess``. The
|
:param auth: Optional. Either ``basic``, ``digest`` or ``guess``. The
|
||||||
default is preemptive Basic auth, sending credentials even if server
|
default is preemptive Basic auth, sending credentials even if server
|
||||||
didn't request them. This saves from an additional roundtrip per
|
didn't request them. This saves from an additional roundtrip per
|
||||||
|
|
|
||||||
|
|
@ -15,21 +15,14 @@ To pin the certificate by fingerprint::
|
||||||
type = "caldav"
|
type = "caldav"
|
||||||
...
|
...
|
||||||
verify_fingerprint = "94:FD:7A:CB:50:75:A4:69:82:0A:F8:23:DF:07:FC:69:3E:CD:90:CA"
|
verify_fingerprint = "94:FD:7A:CB:50:75:A4:69:82:0A:F8:23:DF:07:FC:69:3E:CD:90:CA"
|
||||||
#verify = false # Optional: Disable CA validation, useful for self-signed certs
|
|
||||||
|
|
||||||
SHA1-, SHA256- or MD5-Fingerprints can be used. They're detected by their
|
SHA256-Fingerprints can be used. CA validation is disabled when pinning a
|
||||||
length.
|
fingerprint.
|
||||||
|
|
||||||
You can use the following command for obtaining a SHA-1 fingerprint::
|
You can use the following command for obtaining a SHA-1 fingerprint::
|
||||||
|
|
||||||
echo -n | openssl s_client -connect unterwaditzer.net:443 | openssl x509 -noout -fingerprint
|
echo -n | openssl s_client -connect unterwaditzer.net:443 | openssl x509 -noout -fingerprint
|
||||||
|
|
||||||
Note that ``verify_fingerprint`` doesn't suffice for vdirsyncer to work with
|
|
||||||
self-signed certificates (or certificates that are not in your trust store). You
|
|
||||||
most likely need to set ``verify = false`` as well. This disables verification
|
|
||||||
of the SSL certificate's expiration time and the existence of it in your trust
|
|
||||||
store, all that's verified now is the fingerprint.
|
|
||||||
|
|
||||||
However, please consider using `Let's Encrypt <https://letsencrypt.org/>`_ such
|
However, please consider using `Let's Encrypt <https://letsencrypt.org/>`_ such
|
||||||
that you can forget about all of that. It is easier to deploy a free
|
that you can forget about all of that. It is easier to deploy a free
|
||||||
certificate from them than configuring all of your clients to accept the
|
certificate from them than configuring all of your clients to accept the
|
||||||
|
|
|
||||||
|
|
@ -126,5 +126,4 @@ def test_verify_false_disallowed(aio_connector):
|
||||||
with pytest.raises(ValueError) as excinfo:
|
with pytest.raises(ValueError) as excinfo:
|
||||||
HttpStorage(url="http://example.com", verify=False, connector=aio_connector)
|
HttpStorage(url="http://example.com", verify=False, connector=aio_connector)
|
||||||
|
|
||||||
assert "forbidden" in str(excinfo.value).lower()
|
assert "must be a path to a pem-file." in str(excinfo.value).lower()
|
||||||
assert "consider setting verify_fingerprint" in str(excinfo.value).lower()
|
|
||||||
|
|
|
||||||
|
|
@ -35,6 +35,9 @@ async def test_request_ssl():
|
||||||
)
|
)
|
||||||
assert "certificate verify failed" in str(excinfo.value)
|
assert "certificate verify failed" in str(excinfo.value)
|
||||||
|
|
||||||
|
# XXX FIXME
|
||||||
|
|
||||||
|
with pytest.raises(Exception):
|
||||||
await http.request(
|
await http.request(
|
||||||
"GET",
|
"GET",
|
||||||
"https://self-signed.badssl.com/",
|
"https://self-signed.badssl.com/",
|
||||||
|
|
@ -62,10 +65,12 @@ async def test_request_ssl_leaf_fingerprint(
|
||||||
httpserver.expect_request("/").respond_with_data("OK")
|
httpserver.expect_request("/").respond_with_data("OK")
|
||||||
url = f"https://127.0.0.1:{httpserver.port}/"
|
url = f"https://127.0.0.1:{httpserver.port}/"
|
||||||
|
|
||||||
await http.request("GET", url, verify_fingerprint=fingerprint, session=aio_session)
|
ssl = http.prepare_verify(None, fingerprint)
|
||||||
|
await http.request("GET", url, ssl=ssl, session=aio_session)
|
||||||
|
|
||||||
|
ssl = http.prepare_verify(None, bogus)
|
||||||
with pytest.raises(aiohttp.ServerFingerprintMismatch):
|
with pytest.raises(aiohttp.ServerFingerprintMismatch):
|
||||||
await http.request("GET", url, verify_fingerprint=bogus, session=aio_session)
|
await http.request("GET", url, ssl=ssl, session=aio_session)
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.xfail(reason="Not implemented")
|
@pytest.mark.xfail(reason="Not implemented")
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
import logging
|
import logging
|
||||||
|
from ssl import create_default_context
|
||||||
|
|
||||||
import aiohttp
|
import aiohttp
|
||||||
|
|
||||||
|
|
@ -64,30 +65,23 @@ def prepare_auth(auth, username, password):
|
||||||
|
|
||||||
|
|
||||||
def prepare_verify(verify, verify_fingerprint):
|
def prepare_verify(verify, verify_fingerprint):
|
||||||
if isinstance(verify, (str, bytes)):
|
if isinstance(verify, str):
|
||||||
verify = expand_path(verify)
|
return create_default_context(cafile=expand_path(verify))
|
||||||
elif not isinstance(verify, bool):
|
elif verify is not None:
|
||||||
raise exceptions.UserError(
|
raise exceptions.UserError(
|
||||||
"Invalid value for verify ({}), "
|
f"Invalid value for verify ({verify}), must be a path to a PEM-file."
|
||||||
"must be a path to a PEM-file or boolean.".format(verify)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
if verify_fingerprint is not None:
|
if verify_fingerprint is not None:
|
||||||
if not isinstance(verify_fingerprint, (bytes, str)):
|
if not isinstance(verify_fingerprint, str):
|
||||||
raise exceptions.UserError(
|
raise exceptions.UserError(
|
||||||
"Invalid value for verify_fingerprint "
|
"Invalid value for verify_fingerprint "
|
||||||
"({}), must be a string or null.".format(verify_fingerprint)
|
f"({verify_fingerprint}), must be a string."
|
||||||
)
|
|
||||||
elif not verify:
|
|
||||||
raise exceptions.UserError(
|
|
||||||
"Disabling all SSL validation is forbidden. Consider setting "
|
|
||||||
"verify_fingerprint if you have a broken or self-signed cert."
|
|
||||||
)
|
)
|
||||||
|
|
||||||
return {
|
return aiohttp.Fingerprint(bytes.fromhex(verify_fingerprint.replace(":", "")))
|
||||||
"verify": verify,
|
|
||||||
"verify_fingerprint": verify_fingerprint,
|
return None
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
def prepare_client_cert(cert):
|
def prepare_client_cert(cert):
|
||||||
|
|
@ -99,15 +93,18 @@ def prepare_client_cert(cert):
|
||||||
|
|
||||||
|
|
||||||
async def request(
|
async def request(
|
||||||
method, url, session, latin1_fallback=True, verify_fingerprint=None, **kwargs
|
method,
|
||||||
|
url,
|
||||||
|
session,
|
||||||
|
latin1_fallback=True,
|
||||||
|
**kwargs,
|
||||||
):
|
):
|
||||||
"""
|
"""Wrapper method for requests, to ease logging and mocking.
|
||||||
Wrapper method for requests, to ease logging and mocking. Parameters should
|
|
||||||
be the same as for ``requests.request``, except:
|
Parameters should be the same as for ``aiohttp.request``, as well as:
|
||||||
|
|
||||||
:param session: A requests session object to use.
|
:param session: A requests session object to use.
|
||||||
:param verify_fingerprint: Optional. SHA1 or MD5 fingerprint of the
|
:param verify_fingerprint: Optional. SHA256 of the expected server certificate.
|
||||||
expected server certificate.
|
|
||||||
:param latin1_fallback: RFC-2616 specifies the default Content-Type of
|
:param latin1_fallback: RFC-2616 specifies the default Content-Type of
|
||||||
text/* to be latin1, which is not always correct, but exactly what
|
text/* to be latin1, which is not always correct, but exactly what
|
||||||
requests is doing. Setting this parameter to False will use charset
|
requests is doing. Setting this parameter to False will use charset
|
||||||
|
|
@ -116,13 +113,7 @@ async def request(
|
||||||
https://github.com/kennethreitz/requests/issues/2042
|
https://github.com/kennethreitz/requests/issues/2042
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if verify_fingerprint is not None:
|
# TODO: Support for client-side certifications.
|
||||||
ssl = aiohttp.Fingerprint(bytes.fromhex(verify_fingerprint.replace(":", "")))
|
|
||||||
kwargs.pop("verify", None)
|
|
||||||
elif kwargs.pop("verify", None) is False:
|
|
||||||
ssl = False
|
|
||||||
else:
|
|
||||||
ssl = None # TODO XXX: Check all possible values for this
|
|
||||||
|
|
||||||
session.hooks = {"response": _fix_redirects}
|
session.hooks = {"response": _fix_redirects}
|
||||||
|
|
||||||
|
|
@ -138,29 +129,29 @@ async def request(
|
||||||
|
|
||||||
kwargs.pop("cert", None) # TODO XXX FIXME!
|
kwargs.pop("cert", None) # TODO XXX FIXME!
|
||||||
|
|
||||||
r = await session.request(method, url, ssl=ssl, **kwargs)
|
response = await session.request(method, url, **kwargs)
|
||||||
|
|
||||||
# See https://github.com/kennethreitz/requests/issues/2042
|
# See https://github.com/kennethreitz/requests/issues/2042
|
||||||
content_type = r.headers.get("Content-Type", "")
|
content_type = response.headers.get("Content-Type", "")
|
||||||
if (
|
if (
|
||||||
not latin1_fallback
|
not latin1_fallback
|
||||||
and "charset" not in content_type
|
and "charset" not in content_type
|
||||||
and content_type.startswith("text/")
|
and content_type.startswith("text/")
|
||||||
):
|
):
|
||||||
logger.debug("Removing latin1 fallback")
|
logger.debug("Removing latin1 fallback")
|
||||||
r.encoding = None
|
response.encoding = None
|
||||||
|
|
||||||
logger.debug(r.status)
|
logger.debug(response.status)
|
||||||
logger.debug(r.headers)
|
logger.debug(response.headers)
|
||||||
logger.debug(r.content)
|
logger.debug(response.content)
|
||||||
|
|
||||||
if r.status == 412:
|
if response.status == 412:
|
||||||
raise exceptions.PreconditionFailed(r.reason)
|
raise exceptions.PreconditionFailed(response.reason)
|
||||||
if r.status in (404, 410):
|
if response.status in (404, 410):
|
||||||
raise exceptions.NotFoundError(r.reason)
|
raise exceptions.NotFoundError(response.reason)
|
||||||
|
|
||||||
r.raise_for_status()
|
response.raise_for_status()
|
||||||
return r
|
return response
|
||||||
|
|
||||||
|
|
||||||
def _fix_redirects(r, *args, **kwargs):
|
def _fix_redirects(r, *args, **kwargs):
|
||||||
|
|
|
||||||
|
|
@ -375,7 +375,7 @@ class DAVSession:
|
||||||
url,
|
url,
|
||||||
username="",
|
username="",
|
||||||
password="",
|
password="",
|
||||||
verify=True,
|
verify=None,
|
||||||
auth=None,
|
auth=None,
|
||||||
useragent=USERAGENT,
|
useragent=USERAGENT,
|
||||||
verify_fingerprint=None,
|
verify_fingerprint=None,
|
||||||
|
|
@ -389,7 +389,10 @@ class DAVSession:
|
||||||
auth = prepare_auth(auth, username, password)
|
auth = prepare_auth(auth, username, password)
|
||||||
if auth:
|
if auth:
|
||||||
self._settings["auth"] = auth
|
self._settings["auth"] = auth
|
||||||
self._settings.update(prepare_verify(verify, verify_fingerprint))
|
|
||||||
|
ssl = prepare_verify(verify, verify_fingerprint)
|
||||||
|
if ssl:
|
||||||
|
self._settings["ssl"] = ssl
|
||||||
|
|
||||||
self.useragent = useragent
|
self.useragent = useragent
|
||||||
self.url = url.rstrip("/") + "/"
|
self.url = url.rstrip("/") + "/"
|
||||||
|
|
|
||||||
|
|
@ -27,7 +27,7 @@ class HttpStorage(Storage):
|
||||||
url,
|
url,
|
||||||
username="",
|
username="",
|
||||||
password="",
|
password="",
|
||||||
verify=True,
|
verify=None,
|
||||||
auth=None,
|
auth=None,
|
||||||
useragent=USERAGENT,
|
useragent=USERAGENT,
|
||||||
verify_fingerprint=None,
|
verify_fingerprint=None,
|
||||||
|
|
@ -45,7 +45,10 @@ class HttpStorage(Storage):
|
||||||
auth = prepare_auth(auth, username, password)
|
auth = prepare_auth(auth, username, password)
|
||||||
if auth:
|
if auth:
|
||||||
self._settings["auth"] = auth
|
self._settings["auth"] = auth
|
||||||
self._settings.update(prepare_verify(verify, verify_fingerprint))
|
|
||||||
|
ssl = prepare_verify(verify, verify_fingerprint)
|
||||||
|
if ssl:
|
||||||
|
self._settings["ssl"] = ssl
|
||||||
|
|
||||||
self.username, self.password = username, password
|
self.username, self.password = username, password
|
||||||
self.useragent = useragent
|
self.useragent = useragent
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue