Enforce a Qt with SSL support.
This commit is contained in:
parent
e98a05e53d
commit
a82b0d007d
@ -23,14 +23,8 @@ import collections
|
|||||||
|
|
||||||
from PyQt5.QtCore import (pyqtSlot, pyqtSignal, PYQT_VERSION, QCoreApplication,
|
from PyQt5.QtCore import (pyqtSlot, pyqtSignal, PYQT_VERSION, QCoreApplication,
|
||||||
QUrl)
|
QUrl)
|
||||||
from PyQt5.QtNetwork import QNetworkAccessManager, QNetworkReply, QSslError
|
from PyQt5.QtNetwork import (QNetworkAccessManager, QNetworkReply, QSslError,
|
||||||
|
QSslSocket)
|
||||||
try:
|
|
||||||
from PyQt5.QtNetwork import QSslSocket
|
|
||||||
except ImportError:
|
|
||||||
SSL_AVAILABLE = False
|
|
||||||
else:
|
|
||||||
SSL_AVAILABLE = QSslSocket.supportsSsl()
|
|
||||||
|
|
||||||
from qutebrowser.config import config
|
from qutebrowser.config import config
|
||||||
from qutebrowser.utils import (message, log, usertypes, utils, objreg, qtutils,
|
from qutebrowser.utils import (message, log, usertypes, utils, objreg, qtutils,
|
||||||
@ -46,13 +40,12 @@ _proxy_auth_cache = {}
|
|||||||
|
|
||||||
def init():
|
def init():
|
||||||
"""Disable insecure SSL ciphers on old Qt versions."""
|
"""Disable insecure SSL ciphers on old Qt versions."""
|
||||||
if SSL_AVAILABLE:
|
if not qtutils.version_check('5.3.0'):
|
||||||
if not qtutils.version_check('5.3.0'):
|
# Disable weak SSL ciphers.
|
||||||
# Disable weak SSL ciphers.
|
# See https://codereview.qt-project.org/#/c/75943/
|
||||||
# See https://codereview.qt-project.org/#/c/75943/
|
good_ciphers = [c for c in QSslSocket.supportedCiphers()
|
||||||
good_ciphers = [c for c in QSslSocket.supportedCiphers()
|
if c.usedBits() >= 128]
|
||||||
if c.usedBits() >= 128]
|
QSslSocket.setDefaultCiphers(good_ciphers)
|
||||||
QSslSocket.setDefaultCiphers(good_ciphers)
|
|
||||||
|
|
||||||
|
|
||||||
class SslError(QSslError):
|
class SslError(QSslError):
|
||||||
@ -107,10 +100,9 @@ class NetworkManager(QNetworkAccessManager):
|
|||||||
}
|
}
|
||||||
self._set_cookiejar()
|
self._set_cookiejar()
|
||||||
self._set_cache()
|
self._set_cache()
|
||||||
if SSL_AVAILABLE:
|
self.sslErrors.connect(self.on_ssl_errors)
|
||||||
self.sslErrors.connect(self.on_ssl_errors)
|
self._rejected_ssl_errors = collections.defaultdict(list)
|
||||||
self._rejected_ssl_errors = collections.defaultdict(list)
|
self._accepted_ssl_errors = collections.defaultdict(list)
|
||||||
self._accepted_ssl_errors = collections.defaultdict(list)
|
|
||||||
self.authenticationRequired.connect(self.on_authentication_required)
|
self.authenticationRequired.connect(self.on_authentication_required)
|
||||||
self.proxyAuthenticationRequired.connect(
|
self.proxyAuthenticationRequired.connect(
|
||||||
self.on_proxy_authentication_required)
|
self.on_proxy_authentication_required)
|
||||||
@ -181,76 +173,67 @@ class NetworkManager(QNetworkAccessManager):
|
|||||||
request.deleteLater()
|
request.deleteLater()
|
||||||
self.shutting_down.emit()
|
self.shutting_down.emit()
|
||||||
|
|
||||||
if SSL_AVAILABLE: # pragma: no mccabe
|
@pyqtSlot('QNetworkReply*', 'QList<QSslError>')
|
||||||
@pyqtSlot('QNetworkReply*', 'QList<QSslError>')
|
def on_ssl_errors(self, reply, errors): # pragma: no mccabe
|
||||||
def on_ssl_errors(self, reply, errors):
|
"""Decide if SSL errors should be ignored or not.
|
||||||
"""Decide if SSL errors should be ignored or not.
|
|
||||||
|
|
||||||
This slot is called on SSL/TLS errors by the self.sslErrors signal.
|
This slot is called on SSL/TLS errors by the self.sslErrors signal.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
reply: The QNetworkReply that is encountering the errors.
|
reply: The QNetworkReply that is encountering the errors.
|
||||||
errors: A list of errors.
|
errors: A list of errors.
|
||||||
"""
|
"""
|
||||||
errors = [SslError(e) for e in errors]
|
errors = [SslError(e) for e in errors]
|
||||||
ssl_strict = config.get('network', 'ssl-strict')
|
ssl_strict = config.get('network', 'ssl-strict')
|
||||||
if ssl_strict == 'ask':
|
if ssl_strict == 'ask':
|
||||||
try:
|
try:
|
||||||
host_tpl = urlutils.host_tuple(reply.url())
|
host_tpl = urlutils.host_tuple(reply.url())
|
||||||
except ValueError:
|
except ValueError:
|
||||||
host_tpl = None
|
host_tpl = None
|
||||||
is_accepted = False
|
is_accepted = False
|
||||||
is_rejected = False
|
is_rejected = False
|
||||||
else:
|
else:
|
||||||
is_accepted = set(errors).issubset(
|
is_accepted = set(errors).issubset(
|
||||||
self._accepted_ssl_errors[host_tpl])
|
self._accepted_ssl_errors[host_tpl])
|
||||||
is_rejected = set(errors).issubset(
|
is_rejected = set(errors).issubset(
|
||||||
self._rejected_ssl_errors[host_tpl])
|
self._rejected_ssl_errors[host_tpl])
|
||||||
if is_accepted:
|
if is_accepted:
|
||||||
reply.ignoreSslErrors()
|
reply.ignoreSslErrors()
|
||||||
elif is_rejected:
|
elif is_rejected:
|
||||||
pass
|
|
||||||
else:
|
|
||||||
err_string = '\n'.join('- ' + err.errorString() for err in
|
|
||||||
errors)
|
|
||||||
answer = self._ask('SSL errors - continue?\n{}'.format(
|
|
||||||
err_string), mode=usertypes.PromptMode.yesno,
|
|
||||||
owner=reply)
|
|
||||||
if answer:
|
|
||||||
reply.ignoreSslErrors()
|
|
||||||
d = self._accepted_ssl_errors
|
|
||||||
else:
|
|
||||||
d = self._rejected_ssl_errors
|
|
||||||
if host_tpl is not None:
|
|
||||||
d[host_tpl] += errors
|
|
||||||
elif ssl_strict:
|
|
||||||
pass
|
pass
|
||||||
else:
|
else:
|
||||||
for err in errors:
|
err_string = '\n'.join('- ' + err.errorString() for err in
|
||||||
# FIXME we might want to use warn here (non-fatal error)
|
errors)
|
||||||
# https://github.com/The-Compiler/qutebrowser/issues/114
|
answer = self._ask('SSL errors - continue?\n{}'.format(
|
||||||
message.error(self._win_id,
|
err_string), mode=usertypes.PromptMode.yesno,
|
||||||
'SSL error: {}'.format(err.errorString()))
|
owner=reply)
|
||||||
reply.ignoreSslErrors()
|
if answer:
|
||||||
|
reply.ignoreSslErrors()
|
||||||
|
d = self._accepted_ssl_errors
|
||||||
|
else:
|
||||||
|
d = self._rejected_ssl_errors
|
||||||
|
if host_tpl is not None:
|
||||||
|
d[host_tpl] += errors
|
||||||
|
elif ssl_strict:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
for err in errors:
|
||||||
|
# FIXME we might want to use warn here (non-fatal error)
|
||||||
|
# https://github.com/The-Compiler/qutebrowser/issues/114
|
||||||
|
message.error(self._win_id,
|
||||||
|
'SSL error: {}'.format(err.errorString()))
|
||||||
|
reply.ignoreSslErrors()
|
||||||
|
|
||||||
@pyqtSlot(QUrl)
|
@pyqtSlot(QUrl)
|
||||||
def clear_rejected_ssl_errors(self, url):
|
def clear_rejected_ssl_errors(self, url):
|
||||||
"""Clear the rejected SSL errors on a reload.
|
"""Clear the rejected SSL errors on a reload.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
url: The URL to remove.
|
url: The URL to remove.
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
del self._rejected_ssl_errors[url]
|
del self._rejected_ssl_errors[url]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
pass
|
|
||||||
else:
|
|
||||||
@pyqtSlot(QUrl)
|
|
||||||
def clear_rejected_ssl_errors(self, _url):
|
|
||||||
"""Clear the rejected SSL errors on a reload.
|
|
||||||
|
|
||||||
Does nothing because SSL is unavailable.
|
|
||||||
"""
|
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@pyqtSlot('QNetworkReply', 'QAuthenticator')
|
@pyqtSlot('QNetworkReply', 'QAuthenticator')
|
||||||
@ -334,11 +317,7 @@ class NetworkManager(QNetworkAccessManager):
|
|||||||
A QNetworkReply.
|
A QNetworkReply.
|
||||||
"""
|
"""
|
||||||
scheme = req.url().scheme()
|
scheme = req.url().scheme()
|
||||||
if scheme == 'https' and not SSL_AVAILABLE:
|
if scheme in self._scheme_handlers:
|
||||||
return networkreply.ErrorNetworkReply(
|
|
||||||
req, "SSL is not supported by the installed Qt library!",
|
|
||||||
QNetworkReply.ProtocolUnknownError, self)
|
|
||||||
elif scheme in self._scheme_handlers:
|
|
||||||
return self._scheme_handlers[scheme].createRequest(
|
return self._scheme_handlers[scheme].createRequest(
|
||||||
op, req, outgoing_data)
|
op, req, outgoing_data)
|
||||||
|
|
||||||
|
@ -213,6 +213,19 @@ def check_qt_version():
|
|||||||
_die(text)
|
_die(text)
|
||||||
|
|
||||||
|
|
||||||
|
def check_ssl_support():
|
||||||
|
"""Check if SSL support is available."""
|
||||||
|
try:
|
||||||
|
from PyQt5.QtNetwork import QSslSocket
|
||||||
|
except ImportError:
|
||||||
|
ok = False
|
||||||
|
else:
|
||||||
|
ok = QSslSocket.supportsSsl()
|
||||||
|
if not ok:
|
||||||
|
text = "Fatal error: Your Qt is built without SSL support."
|
||||||
|
_die(text)
|
||||||
|
|
||||||
|
|
||||||
def check_libraries():
|
def check_libraries():
|
||||||
"""Check if all needed Python libraries are installed."""
|
"""Check if all needed Python libraries are installed."""
|
||||||
modules = {
|
modules = {
|
||||||
@ -288,6 +301,7 @@ def earlyinit(args):
|
|||||||
# Now we can be sure QtCore is available, so we can print dialogs on
|
# Now we can be sure QtCore is available, so we can print dialogs on
|
||||||
# errors, so people only using the GUI notice them as well.
|
# errors, so people only using the GUI notice them as well.
|
||||||
check_qt_version()
|
check_qt_version()
|
||||||
|
check_ssl_support()
|
||||||
remove_inputhook()
|
remove_inputhook()
|
||||||
check_libraries()
|
check_libraries()
|
||||||
init_log(args)
|
init_log(args)
|
||||||
|
@ -29,10 +29,7 @@ import collections
|
|||||||
|
|
||||||
from PyQt5.QtCore import QT_VERSION_STR, PYQT_VERSION_STR, qVersion
|
from PyQt5.QtCore import QT_VERSION_STR, PYQT_VERSION_STR, qVersion
|
||||||
from PyQt5.QtWebKit import qWebKitVersion
|
from PyQt5.QtWebKit import qWebKitVersion
|
||||||
try:
|
from PyQt5.QtNetwork import QSslSocket
|
||||||
from PyQt5.QtNetwork import QSslSocket
|
|
||||||
except ImportError:
|
|
||||||
QSslSocket = None
|
|
||||||
|
|
||||||
import qutebrowser
|
import qutebrowser
|
||||||
from qutebrowser.utils import log, utils
|
from qutebrowser.utils import log, utils
|
||||||
@ -199,16 +196,13 @@ def version():
|
|||||||
'Qt: {}, runtime: {}'.format(QT_VERSION_STR, qVersion()),
|
'Qt: {}, runtime: {}'.format(QT_VERSION_STR, qVersion()),
|
||||||
'PyQt: {}'.format(PYQT_VERSION_STR),
|
'PyQt: {}'.format(PYQT_VERSION_STR),
|
||||||
]
|
]
|
||||||
|
|
||||||
lines += _module_versions()
|
lines += _module_versions()
|
||||||
|
|
||||||
if QSslSocket is not None and QSslSocket.supportsSsl():
|
|
||||||
ssl_version = QSslSocket.sslLibraryVersionString()
|
|
||||||
else:
|
|
||||||
ssl_version = 'unavailable'
|
|
||||||
lines += [
|
lines += [
|
||||||
'Webkit: {}'.format(qWebKitVersion()),
|
'Webkit: {}'.format(qWebKitVersion()),
|
||||||
'Harfbuzz: {}'.format(os.environ.get('QT_HARFBUZZ', 'system')),
|
'Harfbuzz: {}'.format(os.environ.get('QT_HARFBUZZ', 'system')),
|
||||||
'SSL: {}'.format(ssl_version),
|
'SSL: {}'.format(QSslSocket.sslLibraryVersionString()),
|
||||||
'',
|
'',
|
||||||
'Frozen: {}'.format(hasattr(sys, 'frozen')),
|
'Frozen: {}'.format(hasattr(sys, 'frozen')),
|
||||||
'Platform: {}, {}'.format(platform.platform(),
|
'Platform: {}, {}'.format(platform.platform(),
|
||||||
|
Loading…
Reference in New Issue
Block a user