[plugin_installer] Add HTTPS Pinning

This commit is contained in:
Philipp Hörist
2017-02-24 00:36:45 +01:00
parent 95c04ebbce
commit 14083c5b30
2 changed files with 86 additions and 17 deletions

View File

@@ -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-----

View File

@@ -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):
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):
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,16 +221,38 @@ 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)
self.thread.start()
def on_error(self, error_text): def on_error(self, reason):
if self.available_plugins_model: if reason == 'CERTIFICATE_VERIFY_FAILED':
for i in range(len(self.available_plugins_model)): YesNoDialog(
self.available_plugins_model[i][Column.UPGRADE] = False _('Security error during download'),
self.progressbar.hide() _('A security error occurred when '
text = GLib.markup_escape_text(error_text) 'downloading. The certificate of the '
WarningDialog(_('Error'), text, self.window) '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()
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,
@@ -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):
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):
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):
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):