From 9d42ebdf2e5d770ca66a0b7f95adbe36bfd7083e Mon Sep 17 00:00:00 2001 From: mathieui Date: Thu, 8 Mar 2012 22:39:30 +0100 Subject: Validate the SSL cert using the TOFU (Trust On First Use) model --- src/core.py | 51 +++++++++++++++++++++++++++++++++++++++++++++++---- src/windows.py | 42 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 89 insertions(+), 4 deletions(-) diff --git a/src/core.py b/src/core.py index 8a4fc7de..91bd0372 100644 --- a/src/core.py +++ b/src/core.py @@ -11,14 +11,12 @@ import os import sys import time import curses -import threading -import traceback +import ssl +from hashlib import sha1 from datetime import datetime from xml.etree import cElementTree as ET -from inspect import getargspec - import common import theming import logging @@ -94,6 +92,7 @@ class Core(object): self.events = events.EventHandler() self.xmpp = singleton.Singleton(connection.Connection) self.xmpp.core = self + self.paused = False self.remote_fifo = None # a unique buffer used to store global informations # that are displayed in almost all tabs, in an @@ -202,6 +201,7 @@ class Core(object): self.xmpp.add_event_handler("chatstate_gone", self.on_chatstate_gone) self.xmpp.add_event_handler("chatstate_inactive", self.on_chatstate_inactive) self.xmpp.add_event_handler("attention", self.on_attention) + self.xmpp.add_event_handler("ssl_cert", self.validate_ssl) self.all_stanzas = Callback('custom matcher', connection.MatchAll(None), self.incoming_stanza) self.xmpp.register_handler(self.all_stanzas) @@ -218,6 +218,44 @@ class Core(object): for plugin in plugins.split(): self.plugin_manager.load(plugin) + def validate_ssl(self, pem): + """ + Check the server certificate using the sleekxmpp ssl_cert event + """ + if config.get('ignore_certificate', 'false').lower() == 'true': + return + cert = config.get('certificate', '') + der = ssl.PEM_cert_to_DER_cert(pem) + found_cert = sha1(der).hexdigest() + if cert: + if found_cert == cert: + log.debug('Cert %s OK', found_cert) + return + else: + saved_input = self.current_tab().input + log.debug('\nWARNING: CERTIFICATE CHANGED old: %s, new: %s\n', cert, found_cert) + input = windows.YesNoInput(text="WARNING! Certificate hash changed to %s. Accept? (y/n)" % found_cert) + self.current_tab().input = input + input.resize(1, self.current_tab().width, self.current_tab().height-1, 0) + input.refresh() + self.doupdate() + self.paused = True + while input.value is None: + pass + self.current_tab().input = saved_input + self.paused = False + if input.value: + self.information('Setting new certificate: old: %s, new: %s' % (cert, found_cert), 'Info') + log.debug('Setting certificate to %s', found_cert) + config.set_and_save('certificate', found_cert) + else: + self.information('You refused to validate the certificate. You are now disconnected', 'Info') + self.xmpp.disconnect() + else: + log.debug('First time. Setting certificate to %s', found_cert) + config.set_and_save('certificate', found_cert) + + def start(self): """ Init curses, create the first tab, etc @@ -965,8 +1003,13 @@ class Core(object): return '\n' return key while self.running: + if self.paused: continue char_list = [common.replace_key_with_bound(key)\ for key in self.read_keyboard()] + if self.paused: + self.current_tab().input.do_command(char_list[0]) + self.current_tab().input.prompt() + continue # Special case for M-x where x is a number if len(char_list) == 1: char = char_list[0] diff --git a/src/windows.py b/src/windows.py index 5b48f052..50cad4c6 100644 --- a/src/windows.py +++ b/src/windows.py @@ -806,6 +806,48 @@ class HelpText(Win): def do_command(self, key, raw=False): return False +class YesNoInput(Win): + """ + A Window just displaying a Yes/No input + Used to ask a confirmation + """ + def __init__(self, text=''): + Win.__init__(self) + self.key_func = { + 'y' : self.on_yes, + 'n' : self.on_no, + } + self.txt = text + self.value = None + + def on_yes(self): + self.value = True + + def on_no(self): + self.value = False + + def refresh(self, txt=None): + log.debug('Refresh: %s',self.__class__.__name__) + if txt: + self.txt = txt + with g_lock: + self._win.erase() + self.addstr(0, 0, self.txt[:self.width-1], to_curses_attr(get_theme().COLOR_INFORMATION_BAR)) + self.finish_line(get_theme().COLOR_INFORMATION_BAR) + self._refresh() + + def do_command(self, key, raw=False): + if key.lower() in self.key_func: + self.key_func[key]() + + def prompt(self): + """Monopolizes the input while waiting for a recognized keypress""" + cl = [] + while self.value is None: + if len(cl) == 1 and cl[0] in self.key_func: + self.key_func[cl[0]]() + cl = self.core.read_keyboard() + class Input(Win): """ The simplest Input possible, provides just a way to edit a single line -- cgit v1.2.3