Commit 46aac301 authored by Yann Leboulanger's avatar Yann Leboulanger

SSL certificate verification, certificate fingerprint verification. fixes #720, #2499

parent 55385c53
......@@ -275,6 +275,7 @@ class Config:
'keyid': [ opt_str, '', '', True ],
'keyname': [ opt_str, '', '', True ],
'usessl': [ opt_bool, False, '', True ],
'ssl_fingerprint_sha1': [ opt_str, '', '', True ],
'use_srv': [ opt_bool, True, '', True ],
'use_custom_host': [ opt_bool, False, '', True ],
'custom_port': [ opt_int, 5222, '', True ],
......
......@@ -79,9 +79,9 @@ class ConfigPaths:
# LOG is deprecated
k = ( 'LOG', 'LOG_DB', 'VCARD', 'AVATAR', 'MY_EMOTS',
'MY_ICONSETS' )
'MY_ICONSETS', 'MY_CACERTS')
v = (u'logs', u'logs.db', u'vcards', u'avatars', u'emoticons',
u'iconsets')
u'iconsets', u'cacerts.pem')
if os.name == 'nt':
v = map(lambda x: x.capitalize(), v)
......
This diff is collapsed.
......@@ -77,6 +77,7 @@ VCARD_PATH = gajimpaths['VCARD']
AVATAR_PATH = gajimpaths['AVATAR']
MY_EMOTS_PATH = gajimpaths['MY_EMOTS']
MY_ICONSETS_PATH = gajimpaths['MY_ICONSETS']
MY_CACERTS = gajimpaths['MY_CACERTS']
TMP = gajimpaths['TMP']
DATA_DIR = gajimpaths['DATA']
HOME_DIR = gajimpaths['HOME']
......
......@@ -745,10 +745,34 @@ class NonBlockingTLS(PlugIn):
#tcpsock._sslContext = OpenSSL.SSL.Context(OpenSSL.SSL.SSLv23_METHOD)
tcpsock.ssl_errnum = 0
tcpsock._sslContext.set_verify(OpenSSL.SSL.VERIFY_PEER, self._ssl_verify_callback)
cacerts = os.path.join(gajim.DATA_DIR, 'other', 'cacerts.pem')
try:
tcpsock._sslContext.load_verify_locations(os.path.join(gajim.DATA_DIR, 'other', 'cacerts.pem'))
tcpsock._sslContext.load_verify_locations(cacerts)
except:
log.warning(_("Unable to load SSL certificats from file %s" % os.path.abspath(os.path.join(gajim.DATA_DIR,'other','ca.crt'))))
log.warning('Unable to load SSL certificats from file %s' % \
os.path.abspath(cacerts))
# load users certs
if os.path.isfile(gajim.MY_CACERTS):
store = tcpsock._sslContext.get_cert_store()
f = open(gajim.MY_CACERTS)
lines = f.readlines()
i = 0
begin = -1
for line in lines:
if 'BEGIN CERTIFICATE' in line:
begin = i
continue
elif 'END CERTIFICATE' in line and begin > -1:
cert = ''.join(lines[begin:i+2])
try:
X509cert = OpenSSL.crypto.load_certificate(
OpenSSL.crypto.FILETYPE_PEM, cert)
store.add_cert(X509cert)
except:
log.warning('Unable to load a certificate from file %s' % \
gajim.MY_CACERTS)
begin = -1
i += 1
tcpsock._sslObj = OpenSSL.SSL.Connection(tcpsock._sslContext, tcpsock._sock)
tcpsock._sslObj.set_connect_state() # set to client mode
......@@ -788,9 +812,12 @@ class NonBlockingTLS(PlugIn):
def _ssl_verify_callback(self, sslconn, cert, errnum, depth, ok):
# Exceptions can't propagate up through this callback, so print them here.
try:
self._owner.Connection.ssl_fingerprint_sha1 = cert.digest('sha1')
if errnum == 0:
return True
self._owner.Connection.ssl_errnum = errnum
self._owner.Connection.ssl_cert_pem = OpenSSL.crypto.dump_certificate(
OpenSSL.crypto.FILETYPE_PEM, cert)
return True
except:
log.error("Exception caught in _ssl_info_callback:", exc_info=True)
......
......@@ -2977,7 +2977,7 @@ class AccountCreationWizardWindow:
def on_wizard_window_destroy(self, widget):
page = self.notebook.get_current_page()
if page > 2 and page < 5 and self.account in gajim.connections:
if page in (4, 5) and self.account in gajim.connections:
# connection instance is saved in gajim.connections and we canceled the
# addition of the account
del gajim.connections[self.account]
......@@ -2994,15 +2994,19 @@ class AccountCreationWizardWindow:
self.window.destroy()
def on_back_button_clicked(self, widget):
if self.notebook.get_current_page() in (1, 2):
cur_page = self.notebook.get_current_page()
if cur_page in (1, 2):
self.notebook.set_current_page(0)
self.back_button.set_sensitive(False)
elif self.notebook.get_current_page() == 3:
elif cur_page == 3:
self.xml.get_widget('form_vbox').remove(self.data_form_widget)
self.notebook.set_current_page(2) # show server page
elif cur_page == 4:
if self.account in gajim.connections:
del gajim.connections[self.account]
self.notebook.set_current_page(2)
self.xml.get_widget('form_vbox').remove(self.data_form_widget)
elif self.notebook.get_current_page() == 5: # finish page
elif cur_page == 6: # finish page
self.forward_button.show()
if self.modify:
self.notebook.set_current_page(1) # Go to parameters page
......@@ -3085,7 +3089,7 @@ class AccountCreationWizardWindow:
self.go_online_checkbutton.show()
img = self.xml.get_widget('finish_image')
img.set_from_stock(gtk.STOCK_APPLY, gtk.ICON_SIZE_DIALOG)
self.notebook.set_current_page(5) # show finish page
self.notebook.set_current_page(6) # show finish page
self.show_vcard_checkbutton.set_active(False)
elif cur_page == 2:
# We are creating a new account
......@@ -3124,7 +3128,7 @@ class AccountCreationWizardWindow:
config['custom_host'] = self.xml.get_widget(
'custom_host_entry').get_text().decode('utf-8')
self.notebook.set_current_page(4) # show creating page
self.notebook.set_current_page(5) # show creating page
self.back_button.hide()
self.forward_button.hide()
self.update_progressbar_timeout_id = gobject.timeout_add(100,
......@@ -3134,6 +3138,18 @@ class AccountCreationWizardWindow:
con.new_account(self.account, config)
gajim.connections[self.account] = con
elif cur_page == 3:
checked = self.xml.get_widget('ssl_checkbutton').get_active()
if checked:
hostname = gajim.connections[self.account].new_account_info[
'hostname']
f = open(gajim.MY_CACERTS, 'a')
f.write(hostname + '\n')
f.write(self.ssl_cert + '\n\n')
f.close()
gajim.connections[self.account].new_account_info[
'ssl_fingerprint_sha1'] = self.ssl_fingerprint
self.notebook.set_current_page(4) # show fom page
elif cur_page == 4:
if self.is_form:
form = self.data_form_widget.data_form
else:
......@@ -3142,7 +3158,7 @@ class AccountCreationWizardWindow:
self.is_form)
self.xml.get_widget('form_vbox').remove(self.data_form_widget)
self.xml.get_widget('progressbar_label').set_markup('<b>Account is being created</b>\n\nPlease wait...')
self.notebook.set_current_page(4) # show creating page
self.notebook.set_current_page(5) # show creating page
self.back_button.hide()
self.forward_button.hide()
self.update_progressbar_timeout_id = gobject.timeout_add(100,
......@@ -3172,13 +3188,13 @@ class AccountCreationWizardWindow:
self.progressbar.pulse()
return True # loop forever
def new_acc_connected(self, form, is_form):
def new_acc_connected(self, form, is_form, ssl_msg, ssl_cert,
ssl_fingerprint):
'''connection to server succeded, present the form to the user.'''
if self.update_progressbar_timeout_id is not None:
gobject.source_remove(self.update_progressbar_timeout_id)
self.back_button.show()
self.forward_button.show()
self.notebook.set_current_page(3) # show form page
self.is_form = is_form
if is_form:
dataform = dataforms.ExtendForm(node = form)
......@@ -3187,6 +3203,21 @@ class AccountCreationWizardWindow:
self.data_form_widget = FakeDataForm(form)
self.data_form_widget.show_all()
self.xml.get_widget('form_vbox').pack_start(self.data_form_widget)
self.ssl_fingerprint = ssl_fingerprint
self.ssl_cert = ssl_cert
if ssl_msg:
# An SSL warning occured, show it
hostname = gajim.connections[self.account].new_account_info['hostname']
self.xml.get_widget('ssl_label').set_markup(_('<b>Security Warning</b>'
'\n\nThe authenticity of the %s SSL certificate could be invalid.\n'
'SSL Error: %s\n'
'Do you still want to connect to this server?') % (hostname,
ssl_msg))
text = _('Add this certificate to the list of trusted certificates.\nSHA1 fingerprint of the certificate:\n%s') % ssl_fingerprint
self.xml.get_widget('ssl_checkbutton').set_label(text)
self.notebook.set_current_page(3) # show SSL page
else:
self.notebook.set_current_page(4) # show form page
def new_acc_not_connected(self, reason):
'''Account creation failed: connection to server failed'''
......@@ -3202,7 +3233,7 @@ class AccountCreationWizardWindow:
finish_text = '<big><b>%s</b></big>\n\n%s' % (
_('An error occurred during account creation') , reason)
self.finish_label.set_markup(finish_text)
self.notebook.set_current_page(5) # show finish page
self.notebook.set_current_page(6) # show finish page
def acc_is_ok(self, config):
'''Account creation succeeded'''
......@@ -3225,7 +3256,7 @@ class AccountCreationWizardWindow:
'button, or later by choosing the Accounts menuitem under the Edit '
'menu from the main window.'))
self.finish_label.set_markup(finish_text)
self.notebook.set_current_page(5) # show finish page
self.notebook.set_current_page(6) # show finish page
if self.update_progressbar_timeout_id is not None:
gobject.source_remove(self.update_progressbar_timeout_id)
......@@ -3242,7 +3273,7 @@ class AccountCreationWizardWindow:
finish_text = '<big><b>%s</b></big>\n\n%s' % (_('An error occurred during '
'account creation') , reason)
self.finish_label.set_markup(finish_text)
self.notebook.set_current_page(5) # show finish page
self.notebook.set_current_page(6) # show finish page
if self.update_progressbar_timeout_id is not None:
gobject.source_remove(self.update_progressbar_timeout_id)
......
......@@ -998,10 +998,11 @@ class Interface:
return
def handle_event_new_acc_connected(self, account, array):
#('NEW_ACC_CONNECTED', account, (infos, is_form))
#('NEW_ACC_CONNECTED', account, (infos, is_form, ssl_msg, ssl_cert,
# ssl_fingerprint))
if self.instances.has_key('account_creation_wizard'):
self.instances['account_creation_wizard'].new_acc_connected(array[0],
array[1])
array[1], array[2], array[3], array[4])
def handle_event_new_acc_not_connected(self, account, array):
#('NEW_ACC_NOT_CONNECTED', account, (reason))
......@@ -2160,6 +2161,45 @@ class Interface:
instance = data[1]
instance.unique_room_id_error(data[0])
def handle_event_ssl_error(self, account, data):
# ('SSL_ERROR', account, (text, cert, sha1_fingerprint))
server = gajim.config.get_per('accounts', account, 'hostname')
def on_ok(is_checked):
if is_checked:
f = open(gajim.MY_CACERTS, 'a')
f.write(server + '\n')
f.write(data[1] + '\n\n')
f.close()
gajim.config.set_per('accounts', account, 'ssl_fingerprint_sha1',
data[2])
gajim.connections[account].ssl_certificate_accepted()
def on_cancel():
gajim.connections[account].disconnect(on_purpose=True)
self.handle_event_status(account, 'offline')
pritext = _('Error verifying SSL certificate')
sectext = _('There was an error verifying the SSL certificate of your jabber server: %(error)s\nDo you still want to connect to this server?') % {'error': data[0]}
checktext = _('Add this certificate to the list of trusted certificates.\nSHA1 fingerprint of the certificate:\n%s') % data[2]
dialogs.ConfirmationDialogCheck(pritext, sectext, checktext,
on_response_ok=on_ok, on_response_cancel=on_cancel)
def handle_event_fingerprint_error(self, account, data):
# ('FINGERPRINT_ERROR', account, (fingerprint,))
def on_yes(widget):
dialog.destroy()
gajim.config.set_per('accounts', account, 'ssl_fingerprint_sha1',
data[0])
gajim.connections[account].ssl_certificate_accepted()
def on_no(widget):
dialog.destroy()
gajim.connections[account].disconnect(on_purpose=True)
self.handle_event_status(account, 'offline')
pritext = _('SSL certificate error')
sectext = _('It seems SSL certificate has changed or your connection is '
'being hacked. Do you still want to connect and update the fingerprint'
'of the certificate?')
dialog = dialogs.YesNoDialog(pritext, sectext, on_response_yes=on_yes,
on_response_no=on_no)
def read_sleepy(self):
'''Check idle status and change that status if needed'''
if not self.sleeper.poll():
......@@ -2497,6 +2537,8 @@ class Interface:
'UNIQUE_ROOM_ID_SUPPORTED': self.handle_event_unique_room_id_supported,
'SESSION_NEG': self.handle_session_negotiation,
'GPG_PASSWORD_REQUIRED': self.handle_event_gpg_password_required,
'SSL_ERROR': self.handle_event_ssl_error,
'FINGERPRINT_ERROR': self.handle_event_fingerprint_error,
}
gajim.handlers = self.handlers
......
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