From a82b0d007dc9bf62eced30b17f3efcaccd2899b6 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Sun, 7 Jun 2015 10:46:47 +0200 Subject: [PATCH] Enforce a Qt with SSL support. --- qutebrowser/browser/network/networkmanager.py | 155 ++++++++---------- qutebrowser/misc/earlyinit.py | 14 ++ qutebrowser/utils/version.py | 12 +- 3 files changed, 84 insertions(+), 97 deletions(-) diff --git a/qutebrowser/browser/network/networkmanager.py b/qutebrowser/browser/network/networkmanager.py index a3fc76baf..aaf5f951b 100644 --- a/qutebrowser/browser/network/networkmanager.py +++ b/qutebrowser/browser/network/networkmanager.py @@ -23,14 +23,8 @@ import collections from PyQt5.QtCore import (pyqtSlot, pyqtSignal, PYQT_VERSION, QCoreApplication, QUrl) -from PyQt5.QtNetwork import QNetworkAccessManager, QNetworkReply, QSslError - -try: - from PyQt5.QtNetwork import QSslSocket -except ImportError: - SSL_AVAILABLE = False -else: - SSL_AVAILABLE = QSslSocket.supportsSsl() +from PyQt5.QtNetwork import (QNetworkAccessManager, QNetworkReply, QSslError, + QSslSocket) from qutebrowser.config import config from qutebrowser.utils import (message, log, usertypes, utils, objreg, qtutils, @@ -46,13 +40,12 @@ _proxy_auth_cache = {} def init(): """Disable insecure SSL ciphers on old Qt versions.""" - if SSL_AVAILABLE: - if not qtutils.version_check('5.3.0'): - # Disable weak SSL ciphers. - # See https://codereview.qt-project.org/#/c/75943/ - good_ciphers = [c for c in QSslSocket.supportedCiphers() - if c.usedBits() >= 128] - QSslSocket.setDefaultCiphers(good_ciphers) + if not qtutils.version_check('5.3.0'): + # Disable weak SSL ciphers. + # See https://codereview.qt-project.org/#/c/75943/ + good_ciphers = [c for c in QSslSocket.supportedCiphers() + if c.usedBits() >= 128] + QSslSocket.setDefaultCiphers(good_ciphers) class SslError(QSslError): @@ -107,10 +100,9 @@ class NetworkManager(QNetworkAccessManager): } self._set_cookiejar() self._set_cache() - if SSL_AVAILABLE: - self.sslErrors.connect(self.on_ssl_errors) - self._rejected_ssl_errors = collections.defaultdict(list) - self._accepted_ssl_errors = collections.defaultdict(list) + self.sslErrors.connect(self.on_ssl_errors) + self._rejected_ssl_errors = collections.defaultdict(list) + self._accepted_ssl_errors = collections.defaultdict(list) self.authenticationRequired.connect(self.on_authentication_required) self.proxyAuthenticationRequired.connect( self.on_proxy_authentication_required) @@ -181,76 +173,67 @@ class NetworkManager(QNetworkAccessManager): request.deleteLater() self.shutting_down.emit() - if SSL_AVAILABLE: # pragma: no mccabe - @pyqtSlot('QNetworkReply*', 'QList') - def on_ssl_errors(self, reply, errors): - """Decide if SSL errors should be ignored or not. + @pyqtSlot('QNetworkReply*', 'QList') + def on_ssl_errors(self, reply, errors): # pragma: no mccabe + """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: - reply: The QNetworkReply that is encountering the errors. - errors: A list of errors. - """ - errors = [SslError(e) for e in errors] - ssl_strict = config.get('network', 'ssl-strict') - if ssl_strict == 'ask': - try: - host_tpl = urlutils.host_tuple(reply.url()) - except ValueError: - host_tpl = None - is_accepted = False - is_rejected = False - else: - is_accepted = set(errors).issubset( - self._accepted_ssl_errors[host_tpl]) - is_rejected = set(errors).issubset( - self._rejected_ssl_errors[host_tpl]) - if is_accepted: - reply.ignoreSslErrors() - 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: + Args: + reply: The QNetworkReply that is encountering the errors. + errors: A list of errors. + """ + errors = [SslError(e) for e in errors] + ssl_strict = config.get('network', 'ssl-strict') + if ssl_strict == 'ask': + try: + host_tpl = urlutils.host_tuple(reply.url()) + except ValueError: + host_tpl = None + is_accepted = False + is_rejected = False + else: + is_accepted = set(errors).issubset( + self._accepted_ssl_errors[host_tpl]) + is_rejected = set(errors).issubset( + self._rejected_ssl_errors[host_tpl]) + if is_accepted: + reply.ignoreSslErrors() + elif is_rejected: 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() + 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 + 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) - def clear_rejected_ssl_errors(self, url): - """Clear the rejected SSL errors on a reload. + @pyqtSlot(QUrl) + def clear_rejected_ssl_errors(self, url): + """Clear the rejected SSL errors on a reload. - Args: - url: The URL to remove. - """ - try: - del self._rejected_ssl_errors[url] - 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. - """ + Args: + url: The URL to remove. + """ + try: + del self._rejected_ssl_errors[url] + except KeyError: pass @pyqtSlot('QNetworkReply', 'QAuthenticator') @@ -334,11 +317,7 @@ class NetworkManager(QNetworkAccessManager): A QNetworkReply. """ scheme = req.url().scheme() - if scheme == 'https' and not SSL_AVAILABLE: - return networkreply.ErrorNetworkReply( - req, "SSL is not supported by the installed Qt library!", - QNetworkReply.ProtocolUnknownError, self) - elif scheme in self._scheme_handlers: + if scheme in self._scheme_handlers: return self._scheme_handlers[scheme].createRequest( op, req, outgoing_data) diff --git a/qutebrowser/misc/earlyinit.py b/qutebrowser/misc/earlyinit.py index 3bc214389..43806df58 100644 --- a/qutebrowser/misc/earlyinit.py +++ b/qutebrowser/misc/earlyinit.py @@ -213,6 +213,19 @@ def check_qt_version(): _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(): """Check if all needed Python libraries are installed.""" modules = { @@ -288,6 +301,7 @@ def earlyinit(args): # 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. check_qt_version() + check_ssl_support() remove_inputhook() check_libraries() init_log(args) diff --git a/qutebrowser/utils/version.py b/qutebrowser/utils/version.py index 19dae311a..add7e4c84 100644 --- a/qutebrowser/utils/version.py +++ b/qutebrowser/utils/version.py @@ -29,10 +29,7 @@ import collections from PyQt5.QtCore import QT_VERSION_STR, PYQT_VERSION_STR, qVersion from PyQt5.QtWebKit import qWebKitVersion -try: - from PyQt5.QtNetwork import QSslSocket -except ImportError: - QSslSocket = None +from PyQt5.QtNetwork import QSslSocket import qutebrowser from qutebrowser.utils import log, utils @@ -199,16 +196,13 @@ def version(): 'Qt: {}, runtime: {}'.format(QT_VERSION_STR, qVersion()), 'PyQt: {}'.format(PYQT_VERSION_STR), ] + lines += _module_versions() - if QSslSocket is not None and QSslSocket.supportsSsl(): - ssl_version = QSslSocket.sslLibraryVersionString() - else: - ssl_version = 'unavailable' lines += [ 'Webkit: {}'.format(qWebKitVersion()), 'Harfbuzz: {}'.format(os.environ.get('QT_HARFBUZZ', 'system')), - 'SSL: {}'.format(ssl_version), + 'SSL: {}'.format(QSslSocket.sslLibraryVersionString()), '', 'Frozen: {}'.format(hasattr(sys, 'frozen')), 'Platform: {}, {}'.format(platform.platform(),