From 14083c5b3020a325790b60b41cb65ce11a4ce9a6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philipp=20H=C3=B6rist?= Date: Fri, 24 Feb 2017 00:36:45 +0100 Subject: [plugin_installer] Add HTTPS Pinning --- plugin_installer/DST_Root_CA_X3.pem | 20 +++++++++ plugin_installer/plugin_installer.py | 83 ++++++++++++++++++++++++++++-------- 2 files changed, 86 insertions(+), 17 deletions(-) create mode 100644 plugin_installer/DST_Root_CA_X3.pem (limited to 'plugin_installer') diff --git a/plugin_installer/DST_Root_CA_X3.pem b/plugin_installer/DST_Root_CA_X3.pem new file mode 100644 index 0000000..300cd7d --- /dev/null +++ b/plugin_installer/DST_Root_CA_X3.pem @@ -0,0 +1,20 @@ +-----BEGIN CERTIFICATE----- +MIIDSjCCAjKgAwIBAgIQRK+wgNajJ7qJMDmGLvhAazANBgkqhkiG9w0BAQUFADA/ +MSQwIgYDVQQKExtEaWdpdGFsIFNpZ25hdHVyZSBUcnVzdCBDby4xFzAVBgNVBAMT +DkRTVCBSb290IENBIFgzMB4XDTAwMDkzMDIxMTIxOVoXDTIxMDkzMDE0MDExNVow +PzEkMCIGA1UEChMbRGlnaXRhbCBTaWduYXR1cmUgVHJ1c3QgQ28uMRcwFQYDVQQD +Ew5EU1QgUm9vdCBDQSBYMzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB +AN+v6ZdQCINXtMxiZfaQguzH0yxrMMpb7NnDfcdAwRgUi+DoM3ZJKuM/IUmTrE4O +rz5Iy2Xu/NMhD2XSKtkyj4zl93ewEnu1lcCJo6m67XMuegwGMoOifooUMM0RoOEq +OLl5CjH9UL2AZd+3UWODyOKIYepLYYHsUmu5ouJLGiifSKOeDNoJjj4XLh7dIN9b +xiqKqy69cK3FCxolkHRyxXtqqzTWMIn/5WgTe1QLyNau7Fqckh49ZLOMxt+/yUFw +7BZy1SbsOFU5Q9D8/RhcQPGX69Wam40dutolucbY38EVAjqr2m7xPi71XAicPNaD +aeQQmxkqtilX4+U9m5/wAl0CAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNV +HQ8BAf8EBAMCAQYwHQYDVR0OBBYEFMSnsaR7LHH62+FLkHX/xBVghYkQMA0GCSqG +SIb3DQEBBQUAA4IBAQCjGiybFwBcqR7uKGY3Or+Dxz9LwwmglSBd49lZRNI+DT69 +ikugdB/OEIKcdBodfpga3csTS7MgROSR6cz8faXbauX+5v3gTt23ADq1cEmv8uXr +AvHRAosZy5Q6XkjEGB5YGV8eAlrwDPGxrancWYaLbumR9YbK+rlmM6pZW87ipxZz +R8srzJmwN0jP41ZL9c8PDHIyh8bwRLtTcm1D9SZImlJnt1ir/md2cXjbDaJWFBM5 +JDGFoqgCWjBH4d1QB7wCCZAA62RjYJsWvIjJEubSfZGL+T0yjWW06XyxV3bqxbYo +Ob8VZRzI9neWagqNdwvYkQsEjgfbKbYK7p2CNTUQ +-----END CERTIFICATE----- \ No newline at end of file diff --git a/plugin_installer/plugin_installer.py b/plugin_installer/plugin_installer.py index 6e9cfb1..4b37e3a 100644 --- a/plugin_installer/plugin_installer.py +++ b/plugin_installer/plugin_installer.py @@ -28,9 +28,11 @@ import io import threading import configparser import os +import ssl import zipfile import logging import posixpath +import urllib.error from urllib.request import urlopen from common import gajim @@ -115,8 +117,7 @@ class PluginInstaller(GajimPlugin): def check_update(self): if hasattr(self, 'thread'): return - self.thread = DownloadAsync(self, check_update=True) - self.thread.start() + self.start_download(check_update=True) self.timeout_id = 0 @log_calls('PluginInstallerPlugin') @@ -211,8 +212,7 @@ class PluginInstaller(GajimPlugin): return if not hasattr(self, 'thread'): self.available_plugins_model.clear() - self.thread = DownloadAsync(self, upgrading=True) - self.thread.start() + self.start_download(upgrading=True) def on_install_upgrade_clicked(self, widget): self.install_button.set_property('sensitive', False) @@ -221,17 +221,39 @@ class PluginInstaller(GajimPlugin): if self.available_plugins_model[i][Column.UPGRADE]: dir_list.append(self.available_plugins_model[i][Column.DIR]) - self.thread = DownloadAsync(self, remote_dirs=dir_list) + self.start_download(remote_dirs=dir_list) + + def on_error(self, reason): + if reason == 'CERTIFICATE_VERIFY_FAILED': + YesNoDialog( + _('Security error during download'), + _('A security error occurred when ' + 'downloading. The certificate of the ' + 'plugin archive could not be verified. ' + 'this might be a security attack. ' + '\n\nYou can continue at your risk. ' + 'Do you want to do so? ' + '(not recommended)' + ), + on_response_yes=lambda dlg: self.start_download( + secure=False, upgrading=True)) + else: + if self.available_plugins_model: + for i in range(len(self.available_plugins_model)): + self.available_plugins_model[i][Column.UPGRADE] = False + self.progressbar.hide() + text = GLib.markup_escape_text(reason) + WarningDialog(_('Error in download'), + _('An error occurred when downloading\n\n' + '[%s]' % (str(text))), self.window) + + def start_download(self, secure=True, remote_dirs=None, + upgrading=False, check_update=False): + self.thread = DownloadAsync( + self, secure=secure, remote_dirs=remote_dirs, + upgrading=upgrading, check_update=check_update) self.thread.start() - def on_error(self, error_text): - if self.available_plugins_model: - for i in range(len(self.available_plugins_model)): - self.available_plugins_model[i][Column.UPGRADE] = False - self.progressbar.hide() - text = GLib.markup_escape_text(error_text) - WarningDialog(_('Error'), text, self.window) - def on_plugin_downloaded(self, plugin_dirs): dialog = HigDialog(None, Gtk.MessageType.INFO, Gtk.ButtonsType.OK, '', _('All selected plugins downloaded')) @@ -329,7 +351,7 @@ class PluginInstaller(GajimPlugin): class DownloadAsync(threading.Thread): - def __init__(self, plugin, remote_dirs=None, + def __init__(self, plugin, secure=True, remote_dirs=None, upgrading=False, check_update=False): threading.Thread.__init__(self) self.plugin = plugin @@ -338,6 +360,7 @@ class DownloadAsync(threading.Thread): self.model = plugin.available_plugins_model self.remote_dirs = remote_dirs self.upgrading = upgrading + self.secure = secure self.check_update = check_update icon = Gtk.Image() self.def_icon = icon.render_icon( @@ -357,9 +380,19 @@ class DownloadAsync(threading.Thread): self.run_check_update() else: self.run_download_plugin_list() - except Exception as e: - GLib.idle_add(self.plugin.on_error, str(e)) + except urllib.error.URLError as exc: + if isinstance(exc.reason, ssl.SSLError): + ssl_reason = exc.reason.reason + if ssl_reason == 'CERTIFICATE_VERIFY_FAILED': + log.exception('Certificate verify failed') + GLib.idle_add(self.plugin.on_error, ssl_reason) + except Exception as exc: + GLib.idle_add(self.plugin.on_error, str(exc)) log.exception('Error fetching plugin list') + finally: + if 'plugins' in gajim.interface.instances: + GLib.source_remove(self.pulse) + GLib.idle_add(self.progressbar.hide) def parse_manifest(self, buf): ''' @@ -380,7 +413,23 @@ class DownloadAsync(threading.Thread): def download_url(self, url): log.debug('Fetching {}'.format(url)) - request = urlopen(url) + ssl_args = {} + if self.secure: + ssl_args['context'] = ssl.create_default_context( + cafile=self.plugin.local_file_path('DST_Root_CA_X3.pem')) + else: + ssl_args['context'] = ssl.create_default_context() + ssl_args['context'].check_hostname = False + ssl_args['context'].verify_mode = ssl.CERT_NONE + + for flag in ('OP_NO_SSLv2', 'OP_NO_SSLv3', + 'OP_NO_TLSv1', 'OP_NO_TLSv1_1', + 'OP_NO_COMPRESSION', + ): + log.info('Installer SSL: +%s' % flag) + ssl_args['context'].options |= getattr(ssl, flag) + request = urlopen(url, **ssl_args) + return io.BytesIO(request.read()) def run_check_update(self): -- cgit v1.2.3