make esession authentication warning less obtrusive

This commit is contained in:
Brendan Taylor 2008-06-29 04:39:29 +00:00
parent 408d3b4ff6
commit b490904454
8 changed files with 206 additions and 60 deletions

View file

@ -0,0 +1,94 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE glade-interface SYSTEM "glade-2.0.dtd">
<!--Generated with glade3 3.4.0 on Sat Jun 28 20:51:00 2008 -->
<glade-interface>
<widget class="GtkDialog" id="esession_info_window">
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
<property name="border_width">5</property>
<property name="window_position">GTK_WIN_POS_CENTER_ON_PARENT</property>
<property name="type_hint">GDK_WINDOW_TYPE_HINT_DIALOG</property>
<property name="has_separator">False</property>
<child internal-child="vbox">
<widget class="GtkVBox" id="dialog-vbox1">
<property name="visible">True</property>
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
<property name="spacing">2</property>
<child>
<widget class="GtkLabel" id="info_display">
<property name="visible">True</property>
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
<property name="label" translatable="yes">(ESession info)</property>
<property name="wrap">True</property>
</widget>
<packing>
<property name="position">1</property>
</packing>
</child>
<child>
<widget class="GtkHBox" id="verification_info">
<property name="visible">True</property>
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
<child>
<widget class="GtkImage" id="warning">
<property name="visible">True</property>
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
<property name="stock">gtk-dialog-warning</property>
</widget>
</child>
<child>
<widget class="GtkLabel" id="label2">
<property name="visible">True</property>
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
<property name="label" translatable="yes">This contact's identity has not been verified.</property>
<property name="wrap">True</property>
<property name="width_chars">0</property>
</widget>
<packing>
<property name="position">1</property>
</packing>
</child>
<child>
<widget class="GtkButton" id="verify_now_button">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
<property name="label" translatable="yes">Verify now</property>
<property name="response_id">0</property>
<signal name="clicked" handler="on_verify_now_button_clicked"/>
</widget>
<packing>
<property name="position">2</property>
</packing>
</child>
</widget>
<packing>
<property name="position">2</property>
</packing>
</child>
<child internal-child="action_area">
<widget class="GtkHButtonBox" id="dialog-action_area1">
<property name="visible">True</property>
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
<property name="layout_style">GTK_BUTTONBOX_END</property>
<child>
<widget class="GtkButton" id="close_button">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
<property name="label" translatable="yes">Close</property>
<property name="response_id">0</property>
<signal name="clicked" handler="on_close_button_clicked"/>
</widget>
</child>
</widget>
<packing>
<property name="expand">False</property>
<property name="pack_type">GTK_PACK_END</property>
</packing>
</child>
</widget>
</child>
</widget>
</glade-interface>

View file

@ -111,12 +111,20 @@
<property name="visible">True</property> <property name="visible">True</property>
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property> <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
<child> <child>
<widget class="GtkImage" id="lock_image"> <widget class="GtkButton" id="authentication_button">
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property> <property name="no_show_all">True</property>
<property name="no_show_all">True</property> <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
<property name="stock">gtk-dialog-authentication</property> <property name="relief">GTK_RELIEF_NONE</property>
<property name="icon_size">1</property> <property name="focus_on_click">False</property>
</widget> <property name="response_id">0</property>
<child>
<widget class="GtkImage" id="lock_image">
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
<property name="stock">gtk-dialog-authentication</property>
<property name="icon_size">1</property>
</widget>
</child>
</widget>
<packing> <packing>
<property name="expand">False</property> <property name="expand">False</property>
<property name="padding">2</property> <property name="padding">2</property>

View file

@ -1056,6 +1056,10 @@ class ChatControl(ChatControlBase):
self.widget_set_visible(self.xml.get_widget('banner_eventbox'), self.widget_set_visible(self.xml.get_widget('banner_eventbox'),
gajim.config.get('hide_chat_banner')) gajim.config.get('hide_chat_banner'))
self.authentication_button = self.xml.get_widget('authentication_button')
id = self.authentication_button.connect('clicked', self._on_authentication_button_clicked)
self.handlers[id] = self.authentication_button
# Add lock image to show chat encryption # Add lock image to show chat encryption
self.lock_image = self.xml.get_widget('lock_image') self.lock_image = self.xml.get_widget('lock_image')
self.lock_tooltip = gtk.Tooltips() self.lock_tooltip = gtk.Tooltips()
@ -1115,11 +1119,18 @@ class ChatControl(ChatControlBase):
self.on_avatar_eventbox_button_press_event) self.on_avatar_eventbox_button_press_event)
self.handlers[id] = widget self.handlers[id] = widget
self.session = session if not session:
session = gajim.connections[self.account].find_controlless_session(self.contact.jid)
self.session = session
if session: if session:
session.control = self session.control = self
self.session = session
# Enable ecryption if needed if session.enable_encryption:
self.print_esession_details()
# Enable encryption if needed
e2e_is_active = hasattr(self, 'session') and self.session and self.session.enable_encryption e2e_is_active = hasattr(self, 'session') and self.session and self.session.enable_encryption
self.gpg_is_active = False self.gpg_is_active = False
gpg_pref = gajim.config.get_per('contacts', contact.jid, 'gpg_enabled') gpg_pref = gajim.config.get_per('contacts', contact.jid, 'gpg_enabled')
@ -1134,7 +1145,7 @@ class ChatControl(ChatControlBase):
if self.session: if self.session:
self.session.loggable = gajim.config.get('log_encrypted_sessions') self.session.loggable = gajim.config.get('log_encrypted_sessions')
self._show_lock_image(self.gpg_is_active, 'GPG', self.gpg_is_active, self.session and \ self._show_lock_image(self.gpg_is_active, 'GPG', self.gpg_is_active, self.session and \
self.session.is_loggable()) self.session.is_loggable(), self.session and self.session.verified_identity)
self.status_tooltip = gtk.Tooltips() self.status_tooltip = gtk.Tooltips()
@ -1365,7 +1376,7 @@ class ChatControl(ChatControlBase):
self.gpg_is_active) self.gpg_is_active)
self._show_lock_image(self.gpg_is_active, 'GPG', self.gpg_is_active, self.session and \ self._show_lock_image(self.gpg_is_active, 'GPG', self.gpg_is_active, self.session and \
self.session.is_loggable()) self.session.is_loggable(), self.session and self.session.verified_identity)
def _show_lock_image(self, visible, enc_type = '', enc_enabled = False, chat_logged = False, authenticated = False): def _show_lock_image(self, visible, enc_type = '', enc_enabled = False, chat_logged = False, authenticated = False):
'''Set lock icon visibility and create tooltip''' '''Set lock icon visibility and create tooltip'''
@ -1373,22 +1384,26 @@ class ChatControl(ChatControlBase):
status_string = enc_enabled and 'is' or 'is NOT' status_string = enc_enabled and 'is' or 'is NOT'
logged_string = chat_logged and 'will' or 'will NOT' logged_string = chat_logged and 'will' or 'will NOT'
if enc_type == 'OTR': if authenticated:
authenticated_string = authenticated \ authenticated_string = ' and authenticated'
and ' and authenticated' \ self.lock_image.set_from_stock('gtk-dialog-authentication', 1)
or ' and NOT authenticated'
else: else:
authenticated_string = '' authenticated_string = ' and NOT authenticated'
self.lock_image.set_from_stock('gtk-dialog-warning', 1)
tooltip = '%s Encryption %s active%s.\n' \ tooltip = '%s encryption %s active%s.\n' \
'Your chat session %s be logged.' % \ 'Your chat session %s be logged.' % \
(enc_type, status_string, authenticated_string, (enc_type, status_string, authenticated_string,
logged_string) logged_string)
self.lock_tooltip.set_tip(self.lock_image, tooltip) self.lock_tooltip.set_tip(self.authentication_button, tooltip)
self.widget_set_visible(self.lock_image, not visible) self.widget_set_visible(self.authentication_button, not visible)
self.lock_image.set_sensitive(enc_enabled) self.lock_image.set_sensitive(enc_enabled)
def _on_authentication_button_clicked(self, widget):
if self.session and self.session.enable_encryption:
dialogs.ESessionInfoWindow(self.session)
def _process_command(self, message): def _process_command(self, message):
if message[0] != '/': if message[0] != '/':
return False return False
@ -1588,11 +1603,15 @@ class ChatControl(ChatControlBase):
msg = _('Session WILL NOT be logged') msg = _('Session WILL NOT be logged')
ChatControlBase.print_conversation_line(self, msg, 'status', '', None) ChatControlBase.print_conversation_line(self, msg, 'status', '', None)
if not self.session.verified_identity:
ChatControlBase.print_conversation_line(self, 'SAS not verified', 'status', '', None)
else: else:
msg = _('E2E encryption disabled') msg = _('E2E encryption disabled')
ChatControlBase.print_conversation_line(self, msg, 'status', '', None) ChatControlBase.print_conversation_line(self, msg, 'status', '', None)
self._show_lock_image(e2e_is_active, 'E2E', e2e_is_active, self.session and \ self._show_lock_image(e2e_is_active, 'E2E', e2e_is_active, self.session and \
self.session.is_loggable()) self.session.is_loggable(), self.session and self.session.verified_identity)
def print_conversation(self, text, frm='', tim=None, encrypted=False, def print_conversation(self, text, frm='', tim=None, encrypted=False,
subject=None, xhtml=None, simple=False): subject=None, xhtml=None, simple=False):

View file

@ -183,6 +183,9 @@ class EncryptedStanzaSession(StanzaSession):
# _o denotes 'other' (ie. the client at the other end of the session) # _o denotes 'other' (ie. the client at the other end of the session)
self._kc_o = None self._kc_o = None
# has the remote contact's identity ever been verified?
self.verified_identity = False
# keep the encrypter updated with my latest cipher key # keep the encrypter updated with my latest cipher key
def set_kc_s(self, value): def set_kc_s(self, value):
self._kc_s = value self._kc_s = value
@ -338,7 +341,8 @@ class EncryptedStanzaSession(StanzaSession):
raise exceptions.NegotiationError, 'calculated m_%s differs from received m_%s' % (i_o, i_o) raise exceptions.NegotiationError, 'calculated m_%s differs from received m_%s' % (i_o, i_o)
if i_o == 'a' and self.sas_algs == 'sas28x5': if i_o == 'a' and self.sas_algs == 'sas28x5':
# XXX not necessary if there's a verified retained secret # we don't need to calculate this if there's a verified retained secret
# (but we do anyways)
self.sas = crypto.sas_28x5(m_o, self.form_s) self.sas = crypto.sas_28x5(m_o, self.form_s)
if self.negotiated['recv_pubkey']: if self.negotiated['recv_pubkey']:
@ -844,26 +848,32 @@ class EncryptedStanzaSession(StanzaSession):
if self.control: if self.control:
self.control.print_esession_details() self.control.print_esession_details()
# calculate and store the new retained secret def do_retained_secret(self, k, old_srs):
# prompt the user to check the remote party's identity (if necessary) '''calculate the new retained secret. determine if the user needs to check the remote party's identity. set up callbacks for when the identity has been verified.'''
def do_retained_secret(self, k, srs):
new_srs = self.hmac(k, 'New Retained Secret') new_srs = self.hmac(k, 'New Retained Secret')
self.srs = new_srs
account = self.conn.name account = self.conn.name
bjid = self.jid.getStripped() bjid = self.jid.getStripped()
if srs: self.verified_identity = False
if secrets.secrets().srs_verified(account, bjid, srs):
secrets.secrets().replace_srs(account, bjid, srs, new_srs, True) if old_srs:
if secrets.secrets().srs_verified(account, bjid, old_srs):
# already had a stored secret verified by the user.
secrets.secrets().replace_srs(account, bjid, old_srs, new_srs, True)
# continue without warning.
self.verified_identity = True
else: else:
def _cb(verified): # had a secret, but it wasn't verified.
secrets.secrets().replace_srs(account, bjid, srs, new_srs, verified) secrets.secrets().replace_srs(account, bjid, old_srs, new_srs, False)
self.check_identity(_cb)
else: else:
def _cb(verified): # we don't even have an SRS
secrets.secrets().save_new_srs(account, bjid, new_srs, verified) secrets.secrets().save_new_srs(account, bjid, new_srs, False)
self.check_identity(_cb) def _verified_srs_cb(self):
secrets.secrets().replace_srs(self.conn.name, self.jid.getStripped(), self.srs, self.srs, True)
def make_dhfield(self, modp_options, sigmai): def make_dhfield(self, modp_options, sigmai):
dhs = [] dhs = []

View file

@ -3780,7 +3780,7 @@ class DataFormWindow(Dialog):
self.dataform_widget.data_form = self.dataform self.dataform_widget.data_form = self.dataform
self.dataform_widget.show_all() self.dataform_widget.show_all()
self.vbox.pack_start(self.dataform_widget) self.vbox.pack_start(self.dataform_widget)
def on_ok(self): def on_ok(self):
form = self.dataform_widget.data_form form = self.dataform_widget.data_form
if isinstance(self.df_response_ok, tuple): if isinstance(self.df_response_ok, tuple):
@ -3788,3 +3788,42 @@ class DataFormWindow(Dialog):
else: else:
self.df_response_ok(form) self.df_response_ok(form)
self.destroy() self.destroy()
class ESessionInfoWindow:
'''Class for displaying information about a XEP-0116 encrypted session'''
def __init__(self, session):
self.session = session
self.xml = gtkgui_helpers.get_glade('esession_info_window.glade')
self.xml.signal_autoconnect(self)
self.update_info()
self.window = self.xml.get_widget('esession_info_window')
self.window.show_all()
def update_info(self):
labeltext = _('''Your chat session with %s is encrypted.\n\nSAS is: %s''') % (self.session.jid, self.session.sas)
if self.session.verified_identity:
labeltext += '\n\n' + _('''You have already verified this contact's identity.''')
w = self.xml.get_widget('verification_info')
w.set_no_show_all(True)
w.hide()
self.xml.get_widget('info_display').set_text(labeltext)
def on_close_button_clicked(self, widget):
self.window.destroy()
def on_verify_now_button_clicked(self, widget):
pritext = _('''Have you verified the remote contact's identity?''')
sectext = _('''To prevent a man-in-the-middle attack, you should speak to this person directly (in person or on the phone) and verify that they see the same SAS as you.\n\nThis session's SAS: %s''') % self.session.sas
sectext += '\n\n' + _('Did you talk to the remote contact and verify the SAS?')
dialog = YesNoDialog(pritext, sectext)
if dialog.get_response() == gtk.RESPONSE_YES:
self.session._verified_srs_cb()
self.session.verified_identity = True
self.update_info()

View file

@ -14,23 +14,6 @@ def describe_features(features):
elif features['logging'] == 'mustnot': elif features['logging'] == 'mustnot':
return _('- messages will not be logged') return _('- messages will not be logged')
def show_sas_dialog(session, jid, sas, on_success):
def success_cb(checked):
on_success(checked)
def failure_cb():
session.reject_negotiation()
dialogs.ConfirmationDialogCheck(_('''OK to continue with negotiation?'''),
_('''You've begun an encrypted session with %s, but it can't be guaranteed that you're talking directly to the person you think you are.
You should speak with them directly (in person or on the phone) and confirm that their Short Authentication String is identical to this one: %s
Would you like to continue with the encrypted session?''') % (jid, sas),
_('Yes, I verified the Short Authentication String'),
on_response_ok=success_cb, on_response_cancel=failure_cb, is_modal=False)
class FeatureNegotiationWindow: class FeatureNegotiationWindow:
'''FeatureNegotiotionWindow class''' '''FeatureNegotiotionWindow class'''
def __init__(self, account, jid, session, form): def __init__(self, account, jid, session, form):
@ -67,8 +50,6 @@ class FeatureNegotiationWindow:
self.window.destroy() self.window.destroy()
def on_cancel_button_clicked(self, widget): def on_cancel_button_clicked(self, widget):
# XXX determine whether to reveal presence
rejection = xmpp.Message(self.jid) rejection = xmpp.Message(self.jid)
rejection.setThread(self.session.thread_id) rejection.setThread(self.session.thread_id)
feature = rejection.NT.feature feature = rejection.NT.feature
@ -80,8 +61,6 @@ class FeatureNegotiationWindow:
feature.addChild(node=x) feature.addChild(node=x)
# XXX optional <body/>
gajim.connections[self.account].send_stanza(rejection) gajim.connections[self.account].send_stanza(rejection)
self.window.destroy() self.window.destroy()

View file

@ -136,7 +136,7 @@ class Secrets:
# has the user verified this retained secret? # has the user verified this retained secret?
def srs_verified(self, account, jid, srs): def srs_verified(self, account, jid, srs):
return self.find_srs(account, jid, srs)[1] return self.find_srs(account, jid, srs)[1]
def replace_srs(self, account, jid, old_secret, new_secret, verified): def replace_srs(self, account, jid, old_secret, new_secret, verified):
our_secrets = self.srs[account][jid] our_secrets = self.srs[account][jid]

View file

@ -334,9 +334,6 @@ class ChatControlSession(stanza_session.EncryptedStanzaSession):
# ---- ESessions stuff --- # ---- ESessions stuff ---
def check_identity(self, on_success):
negotiation.show_sas_dialog(self, self.jid, self.sas, on_success)
def handle_negotiation(self, form): def handle_negotiation(self, form):
if form.getField('accept') and not form['accept'] in ('1', 'true'): if form.getField('accept') and not form['accept'] in ('1', 'true'):
self.cancelled_negotiation() self.cancelled_negotiation()