Commit 14083c5b authored by Philipp Hörist's avatar Philipp Hörist

[plugin_installer] Add HTTPS Pinning

parent 95c04ebb
-----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
...@@ -28,9 +28,11 @@ import io ...@@ -28,9 +28,11 @@ import io
import threading import threading
import configparser import configparser
import os import os
import ssl
import zipfile import zipfile
import logging import logging
import posixpath import posixpath
import urllib.error
from urllib.request import urlopen from urllib.request import urlopen
from common import gajim from common import gajim
...@@ -115,8 +117,7 @@ class PluginInstaller(GajimPlugin): ...@@ -115,8 +117,7 @@ class PluginInstaller(GajimPlugin):
def check_update(self): def check_update(self):
if hasattr(self, 'thread'): if hasattr(self, 'thread'):
return return
self.thread = DownloadAsync(self, check_update=True) self.start_download(check_update=True)
self.thread.start()
self.timeout_id = 0 self.timeout_id = 0
@log_calls('PluginInstallerPlugin') @log_calls('PluginInstallerPlugin')
...@@ -211,8 +212,7 @@ class PluginInstaller(GajimPlugin): ...@@ -211,8 +212,7 @@ class PluginInstaller(GajimPlugin):
return return
if not hasattr(self, 'thread'): if not hasattr(self, 'thread'):
self.available_plugins_model.clear() self.available_plugins_model.clear()
self.thread = DownloadAsync(self, upgrading=True) self.start_download(upgrading=True)
self.thread.start()
def on_install_upgrade_clicked(self, widget): def on_install_upgrade_clicked(self, widget):
self.install_button.set_property('sensitive', False) self.install_button.set_property('sensitive', False)
...@@ -221,17 +221,39 @@ class PluginInstaller(GajimPlugin): ...@@ -221,17 +221,39 @@ class PluginInstaller(GajimPlugin):
if self.available_plugins_model[i][Column.UPGRADE]: if self.available_plugins_model[i][Column.UPGRADE]:
dir_list.append(self.available_plugins_model[i][Column.DIR]) 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'
'<tt>[%s]</tt>' % (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() 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): def on_plugin_downloaded(self, plugin_dirs):
dialog = HigDialog(None, Gtk.MessageType.INFO, Gtk.ButtonsType.OK, dialog = HigDialog(None, Gtk.MessageType.INFO, Gtk.ButtonsType.OK,
'', _('All selected plugins downloaded')) '', _('All selected plugins downloaded'))
...@@ -329,7 +351,7 @@ class PluginInstaller(GajimPlugin): ...@@ -329,7 +351,7 @@ class PluginInstaller(GajimPlugin):
class DownloadAsync(threading.Thread): 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): upgrading=False, check_update=False):
threading.Thread.__init__(self) threading.Thread.__init__(self)
self.plugin = plugin self.plugin = plugin
...@@ -338,6 +360,7 @@ class DownloadAsync(threading.Thread): ...@@ -338,6 +360,7 @@ class DownloadAsync(threading.Thread):
self.model = plugin.available_plugins_model self.model = plugin.available_plugins_model
self.remote_dirs = remote_dirs self.remote_dirs = remote_dirs
self.upgrading = upgrading self.upgrading = upgrading
self.secure = secure
self.check_update = check_update self.check_update = check_update
icon = Gtk.Image() icon = Gtk.Image()
self.def_icon = icon.render_icon( self.def_icon = icon.render_icon(
...@@ -357,9 +380,19 @@ class DownloadAsync(threading.Thread): ...@@ -357,9 +380,19 @@ class DownloadAsync(threading.Thread):
self.run_check_update() self.run_check_update()
else: else:
self.run_download_plugin_list() self.run_download_plugin_list()
except Exception as e: except urllib.error.URLError as exc:
GLib.idle_add(self.plugin.on_error, str(e)) 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') 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): def parse_manifest(self, buf):
''' '''
...@@ -380,7 +413,23 @@ class DownloadAsync(threading.Thread): ...@@ -380,7 +413,23 @@ class DownloadAsync(threading.Thread):
def download_url(self, url): def download_url(self, url):
log.debug('Fetching {}'.format(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()) return io.BytesIO(request.read())
def run_check_update(self): def run_check_update(self):
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment