realtime notification of esession begin/end
This commit is contained in:
		
							parent
							
								
									00ad2846c0
								
							
						
					
					
						commit
						88f017a20d
					
				
					 4 changed files with 80 additions and 58 deletions
				
			
		| 
						 | 
				
			
			@ -1057,10 +1057,7 @@ class ChatControl(ChatControlBase):
 | 
			
		|||
		if self.contact.jid in gajim.encrypted_chats[self.account]:
 | 
			
		||||
			self.xml.get_widget('gpg_togglebutton').set_active(True)
 | 
			
		||||
 | 
			
		||||
		self.session = session
 | 
			
		||||
 | 
			
		||||
		# does this window have an existing, active esession?
 | 
			
		||||
		self.esessioned = False
 | 
			
		||||
		self.set_session(session)
 | 
			
		||||
 | 
			
		||||
		self.status_tooltip = gtk.Tooltips()
 | 
			
		||||
		self.update_ui()
 | 
			
		||||
| 
						 | 
				
			
			@ -1467,6 +1464,23 @@ class ChatControl(ChatControlBase):
 | 
			
		|||
		self.mouse_over_in_last_30_secs = False
 | 
			
		||||
		self.kbd_activity_in_last_30_secs = False
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
	# print esession settings to textview
 | 
			
		||||
	def print_esession_details(self):
 | 
			
		||||
		if self.session and self.session.enable_encryption:
 | 
			
		||||
			msg = _('E2E encryption enabled')
 | 
			
		||||
			ChatControlBase.print_conversation_line(self, msg, 'status', '', None)
 | 
			
		||||
 | 
			
		||||
			if self.session.loggable:
 | 
			
		||||
				msg = _('Session WILL be logged')
 | 
			
		||||
			else:
 | 
			
		||||
				msg = _('Session WILL NOT be logged')
 | 
			
		||||
 | 
			
		||||
			ChatControlBase.print_conversation_line(self, msg, 'status', '', None)
 | 
			
		||||
		else:
 | 
			
		||||
			msg = _('E2E encryption disabled')
 | 
			
		||||
			ChatControlBase.print_conversation_line(self, msg, 'status', '', None)
 | 
			
		||||
 | 
			
		||||
	def print_conversation(self, text, frm = '', tim = None,
 | 
			
		||||
		encrypted = False, subject = None, xhtml = None):
 | 
			
		||||
		'''Print a line in the conversation:
 | 
			
		||||
| 
						 | 
				
			
			@ -1486,41 +1500,21 @@ class ChatControl(ChatControlBase):
 | 
			
		|||
			kind = 'info'
 | 
			
		||||
			name = ''
 | 
			
		||||
		else:
 | 
			
		||||
			# ESessions
 | 
			
		||||
			if self.session and self.session.enable_encryption:
 | 
			
		||||
				if not self.esessioned:
 | 
			
		||||
					msg = _('Encryption enabled')
 | 
			
		||||
					ChatControlBase.print_conversation_line(self, msg, 
 | 
			
		||||
						'status', '', tim)
 | 
			
		||||
 | 
			
		||||
					if self.session.loggable:
 | 
			
		||||
						msg = _('Session WILL be logged')
 | 
			
		||||
					else:
 | 
			
		||||
						msg = _('Session WILL NOT be logged')
 | 
			
		||||
 | 
			
		||||
					ChatControlBase.print_conversation_line(self, msg, 
 | 
			
		||||
						'status', '', tim)
 | 
			
		||||
 | 
			
		||||
					self.esessioned = True
 | 
			
		||||
				elif not encrypted:
 | 
			
		||||
				if not encrypted:
 | 
			
		||||
					msg = _('The following message was NOT encrypted')
 | 
			
		||||
					ChatControlBase.print_conversation_line(self, msg, 
 | 
			
		||||
						'status', '', tim)
 | 
			
		||||
			elif self.esessioned:
 | 
			
		||||
				msg = _('Encryption disabled')
 | 
			
		||||
				ChatControlBase.print_conversation_line(self, msg, 
 | 
			
		||||
					'status', '', tim)
 | 
			
		||||
				self.esessioned = False
 | 
			
		||||
			else:
 | 
			
		||||
				# GPG encryption
 | 
			
		||||
				ec = gajim.encrypted_chats[self.account]
 | 
			
		||||
				if encrypted and jid not in ec:
 | 
			
		||||
					msg = _('Encryption enabled')
 | 
			
		||||
					msg = _('OpenPGP Encryption enabled')
 | 
			
		||||
					ChatControlBase.print_conversation_line(self, msg, 
 | 
			
		||||
						'status', '', tim)
 | 
			
		||||
					ec.append(jid)
 | 
			
		||||
				elif not encrypted and jid in ec:
 | 
			
		||||
					msg = _('Encryption disabled')
 | 
			
		||||
					msg = _('OpenPGP Encryption disabled')
 | 
			
		||||
					ChatControlBase.print_conversation_line(self, msg,
 | 
			
		||||
						'status', '', tim)
 | 
			
		||||
					ec.remove(jid)
 | 
			
		||||
| 
						 | 
				
			
			@ -2003,12 +1997,16 @@ class ChatControl(ChatControlBase):
 | 
			
		|||
 | 
			
		||||
	def read_queue(self):
 | 
			
		||||
		'''read queue and print messages containted in it'''
 | 
			
		||||
 | 
			
		||||
		jid = self.contact.jid
 | 
			
		||||
		jid_with_resource = jid
 | 
			
		||||
		if self.resource:
 | 
			
		||||
			jid_with_resource += '/' + self.resource
 | 
			
		||||
		events = gajim.events.get_events(self.account, jid_with_resource)
 | 
			
		||||
 | 
			
		||||
		if hasattr(self, 'session') and self.session and self.session.enable_encryption:
 | 
			
		||||
			self.print_esession_details()
 | 
			
		||||
 | 
			
		||||
		# Is it a pm ?
 | 
			
		||||
		is_pm = False
 | 
			
		||||
		room_jid, nick = gajim.get_room_and_nick_from_fjid(jid)
 | 
			
		||||
| 
						 | 
				
			
			@ -2169,18 +2167,18 @@ class ChatControl(ChatControlBase):
 | 
			
		|||
			msg = _('Encryption disabled')
 | 
			
		||||
			ChatControlBase.print_conversation_line(self, msg, 
 | 
			
		||||
				'status', '', None)
 | 
			
		||||
			self.esessioned = False
 | 
			
		||||
 | 
			
		||||
			jid = str(self.session.jid)
 | 
			
		||||
 | 
			
		||||
			gajim.connections[self.account].delete_session(jid,
 | 
			
		||||
				self.session.thread_id)
 | 
			
		||||
 | 
			
		||||
			self.session = gajim.connections[self.account].make_new_session(jid)
 | 
			
		||||
			self.set_session(gajim.connections[self.account].make_new_session(jid))
 | 
			
		||||
		else:
 | 
			
		||||
			if not self.session:
 | 
			
		||||
				self.session = gajim.connections[self.account].make_new_session(
 | 
			
		||||
					self.contact.jid)
 | 
			
		||||
				fjid = self.contact.get_full_jid()
 | 
			
		||||
				new_sess = gajim.connections[self.account].make_new_session(fjid)
 | 
			
		||||
				self.set_session(new_sess)
 | 
			
		||||
 | 
			
		||||
			# XXX decide whether to use 4 or 3 message negotiation
 | 
			
		||||
			self.session.negotiate_e2e(False)
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -71,8 +71,10 @@ class StanzaSession(object):
 | 
			
		|||
	def cancelled_negotiation(self):
 | 
			
		||||
		'''A negotiation has been cancelled, so reset this session to its default state.'''
 | 
			
		||||
 | 
			
		||||
		# XXX notify the user	
 | 
			
		||||
		
 | 
			
		||||
		if hasattr(self, 'control'):
 | 
			
		||||
			msg = _('Session negotiation cancelled')
 | 
			
		||||
			self.control.print_conversation_line(self, msg, 'status', '', None)
 | 
			
		||||
 | 
			
		||||
		self.status = None
 | 
			
		||||
		self.negotiated = {}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -92,7 +94,7 @@ class StanzaSession(object):
 | 
			
		|||
		self.status = None
 | 
			
		||||
 | 
			
		||||
	def acknowledge_termination(self):
 | 
			
		||||
		# we could send an acknowledgement message here, but we won't.
 | 
			
		||||
		# we could send an acknowledgement message to the remote client here
 | 
			
		||||
		self.status = None
 | 
			
		||||
 | 
			
		||||
if gajim.HAVE_PYCRYPTO:
 | 
			
		||||
| 
						 | 
				
			
			@ -105,7 +107,7 @@ if gajim.HAVE_PYCRYPTO:
 | 
			
		|||
	import secrets
 | 
			
		||||
 | 
			
		||||
# an encrypted stanza negotiation has several states. i've represented them
 | 
			
		||||
# as the following values in the 'status' 
 | 
			
		||||
# as the following values in the 'status'
 | 
			
		||||
# attribute of the session object:
 | 
			
		||||
 | 
			
		||||
# 1. None:
 | 
			
		||||
| 
						 | 
				
			
			@ -143,7 +145,7 @@ class EncryptedStanzaSession(StanzaSession):
 | 
			
		|||
 | 
			
		||||
		# _s denotes 'self' (ie. this client)
 | 
			
		||||
		self._kc_s = None
 | 
			
		||||
	
 | 
			
		||||
 | 
			
		||||
		# _o denotes 'other' (ie. the client at the other end of the session)
 | 
			
		||||
		self._kc_o = None
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -161,17 +163,17 @@ class EncryptedStanzaSession(StanzaSession):
 | 
			
		|||
		self._kc_o = value
 | 
			
		||||
		self.decrypter = self.cipher.new(self._kc_o, self.cipher.MODE_CTR,
 | 
			
		||||
			counter=self.decryptcounter)
 | 
			
		||||
	
 | 
			
		||||
 | 
			
		||||
	def get_kc_o(self):
 | 
			
		||||
		return self._kc_o
 | 
			
		||||
 | 
			
		||||
	kc_s = property(get_kc_s, set_kc_s)
 | 
			
		||||
	kc_o = property(get_kc_o, set_kc_o)
 | 
			
		||||
	
 | 
			
		||||
 | 
			
		||||
	def encryptcounter(self):
 | 
			
		||||
		self.c_s = (self.c_s + 1) % (2 ** self.n)
 | 
			
		||||
		return crypto.encode_mpi_with_padding(self.c_s)
 | 
			
		||||
	
 | 
			
		||||
 | 
			
		||||
	def decryptcounter(self):
 | 
			
		||||
		self.c_o = (self.c_o + 1) % (2 ** self.n)
 | 
			
		||||
		return crypto.encode_mpi_with_padding(self.c_o)
 | 
			
		||||
| 
						 | 
				
			
			@ -231,7 +233,7 @@ class EncryptedStanzaSession(StanzaSession):
 | 
			
		|||
 | 
			
		||||
	def decompress(self, compressed):
 | 
			
		||||
		if self.compression == None:
 | 
			
		||||
			return compressed 
 | 
			
		||||
			return compressed
 | 
			
		||||
 | 
			
		||||
	def encrypt(self, encryptable):
 | 
			
		||||
		padded = crypto.pad_to_multiple(encryptable, 16, ' ', False)
 | 
			
		||||
| 
						 | 
				
			
			@ -343,7 +345,7 @@ class EncryptedStanzaSession(StanzaSession):
 | 
			
		|||
			content += self.form_o + form_o2
 | 
			
		||||
 | 
			
		||||
		mac_o_calculated = self.hmac(self.ks_o, content)
 | 
			
		||||
	
 | 
			
		||||
 | 
			
		||||
		if self.negotiated['recv_pubkey']:
 | 
			
		||||
			hash = crypto.sha256(mac_o_calculated)
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -380,7 +382,7 @@ class EncryptedStanzaSession(StanzaSession):
 | 
			
		|||
			if self.negotiated['send_pubkey'] == 'hash':
 | 
			
		||||
				b64ed = base64.b64encode(self.hash(pubkey_s))
 | 
			
		||||
				pubkey_s = '<fingerprint>%s</fingerprint>' % b64ed
 | 
			
		||||
		
 | 
			
		||||
 | 
			
		||||
			id_s = self.encrypt(pubkey_s + sign_s)
 | 
			
		||||
		else:
 | 
			
		||||
			id_s = self.encrypt(mac_s)
 | 
			
		||||
| 
						 | 
				
			
			@ -395,7 +397,7 @@ class EncryptedStanzaSession(StanzaSession):
 | 
			
		|||
			if self.sigmai:
 | 
			
		||||
				# XXX save retained secret?
 | 
			
		||||
				self.check_identity(lambda : ())
 | 
			
		||||
		
 | 
			
		||||
 | 
			
		||||
		return (xmpp.DataField(name='identity', value=base64.b64encode(id_s)), \
 | 
			
		||||
						xmpp.DataField(name='mac', value=base64.b64encode(m_s)))
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -437,7 +439,7 @@ class EncryptedStanzaSession(StanzaSession):
 | 
			
		|||
		x.addChild(node=xmpp.DataField(name='sign_algs', value='http://www.w3.org/2000/09/xmldsig#rsa-sha256', typ='hidden'))
 | 
			
		||||
 | 
			
		||||
		self.n_s = crypto.generate_nonce()
 | 
			
		||||
	
 | 
			
		||||
 | 
			
		||||
		x.addChild(node=xmpp.DataField(name='my_nonce', value=base64.b64encode(self.n_s), typ='hidden'))
 | 
			
		||||
 | 
			
		||||
		modp_options = [ 5, 14, 2, 1 ]
 | 
			
		||||
| 
						 | 
				
			
			@ -454,7 +456,7 @@ class EncryptedStanzaSession(StanzaSession):
 | 
			
		|||
		self.status = 'requested-e2e'
 | 
			
		||||
 | 
			
		||||
		self.send(request)
 | 
			
		||||
	
 | 
			
		||||
 | 
			
		||||
	# 4.3 esession response (bob)
 | 
			
		||||
	def verify_options_bob(self, form):
 | 
			
		||||
		negotiated = {'recv_pubkey': None, 'send_pubkey': None}
 | 
			
		||||
| 
						 | 
				
			
			@ -570,8 +572,8 @@ class EncryptedStanzaSession(StanzaSession):
 | 
			
		|||
		self.d = crypto.powmod(g, self.y, p)
 | 
			
		||||
 | 
			
		||||
		to_add = { 'my_nonce': self.n_s,
 | 
			
		||||
							 'dhkeys': crypto.encode_mpi(self.d), 
 | 
			
		||||
							 'counter': crypto.encode_mpi(self.c_o), 
 | 
			
		||||
							 'dhkeys': crypto.encode_mpi(self.d),
 | 
			
		||||
							 'counter': crypto.encode_mpi(self.c_o),
 | 
			
		||||
							 'nonce': self.n_o }
 | 
			
		||||
 | 
			
		||||
		for name in to_add:
 | 
			
		||||
| 
						 | 
				
			
			@ -666,7 +668,7 @@ class EncryptedStanzaSession(StanzaSession):
 | 
			
		|||
			self.kc_o, self.km_o, self.ks_o = self.generate_responder_keys(self.k)
 | 
			
		||||
			self.verify_identity(form, self.d, True, 'b')
 | 
			
		||||
		else:
 | 
			
		||||
			srses = secrets.secrets().retained_secrets(self.conn.name, self.jid.getStripped()) 
 | 
			
		||||
			srses = secrets.secrets().retained_secrets(self.conn.name, self.jid.getStripped())
 | 
			
		||||
			rshashes = [self.hmac(self.n_s, rs) for (rs,v) in srses]
 | 
			
		||||
 | 
			
		||||
			if not rshashes:
 | 
			
		||||
| 
						 | 
				
			
			@ -677,7 +679,7 @@ class EncryptedStanzaSession(StanzaSession):
 | 
			
		|||
			rshashes = [base64.b64encode(rshash) for rshash in rshashes]
 | 
			
		||||
			result.addChild(node=xmpp.DataField(name='rshashes', value=rshashes))
 | 
			
		||||
			result.addChild(node=xmpp.DataField(name='dhkeys', value=base64.b64encode(crypto.encode_mpi(e))))
 | 
			
		||||
		
 | 
			
		||||
 | 
			
		||||
			self.form_o = ''.join(map(lambda el: xmpp.c14n.c14n(el), form.getChildren()))
 | 
			
		||||
 | 
			
		||||
		# MUST securely destroy K unless it will be used later to generate the final shared secret
 | 
			
		||||
| 
						 | 
				
			
			@ -687,13 +689,13 @@ class EncryptedStanzaSession(StanzaSession):
 | 
			
		|||
 | 
			
		||||
		feature.addChild(node=result)
 | 
			
		||||
		self.send(accept)
 | 
			
		||||
	
 | 
			
		||||
 | 
			
		||||
		if self.sigmai:
 | 
			
		||||
			self.status = 'active'
 | 
			
		||||
			self.enable_encryption = True
 | 
			
		||||
		else:
 | 
			
		||||
			self.status = 'identified-alice'
 | 
			
		||||
	
 | 
			
		||||
 | 
			
		||||
	# 4.5 esession accept (bob)
 | 
			
		||||
	def accept_e2e_bob(self, form):
 | 
			
		||||
		response = xmpp.Message()
 | 
			
		||||
| 
						 | 
				
			
			@ -724,7 +726,7 @@ class EncryptedStanzaSession(StanzaSession):
 | 
			
		|||
		# 4.5.4 generating bob's final session keys
 | 
			
		||||
 | 
			
		||||
		srs = ''
 | 
			
		||||
	
 | 
			
		||||
 | 
			
		||||
		srses = secrets.secrets().retained_secrets(self.conn.name, self.jid.getStripped())
 | 
			
		||||
		rshashes = [base64.b64decode(rshash) for rshash in form.getField('rshashes').getValues()]
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -767,6 +769,9 @@ class EncryptedStanzaSession(StanzaSession):
 | 
			
		|||
		self.status = 'active'
 | 
			
		||||
		self.enable_encryption = True
 | 
			
		||||
 | 
			
		||||
		if hasattr(self, 'control'):
 | 
			
		||||
			self.control.print_esession_details()
 | 
			
		||||
 | 
			
		||||
	def final_steps_alice(self, form):
 | 
			
		||||
		srs = ''
 | 
			
		||||
		srses = secrets.secrets().retained_secrets(self.conn.name, self.jid.getStripped())
 | 
			
		||||
| 
						 | 
				
			
			@ -793,13 +798,16 @@ class EncryptedStanzaSession(StanzaSession):
 | 
			
		|||
 | 
			
		||||
		self.verify_identity(form, self.d, False, 'b')
 | 
			
		||||
# Note: If Alice discovers an error then she SHOULD ignore any encrypted content she received in the stanza.
 | 
			
		||||
	
 | 
			
		||||
 | 
			
		||||
		if self.negotiated['logging'] == 'mustnot':
 | 
			
		||||
			self.loggable = False
 | 
			
		||||
 | 
			
		||||
		self.status = 'active'
 | 
			
		||||
		self.enable_encryption = True
 | 
			
		||||
 | 
			
		||||
		if hasattr(self, 'control'):
 | 
			
		||||
			self.control.print_esession_details()
 | 
			
		||||
 | 
			
		||||
	# calculate and store the new retained secret
 | 
			
		||||
	# prompt the user to check the remote party's identity (if necessary)
 | 
			
		||||
	def do_retained_secret(self, k, srs):
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -2031,7 +2031,8 @@ class Interface:
 | 
			
		|||
				ctrl = gajim.interface.msg_win_mgr.get_control(str(jid), account)
 | 
			
		||||
 | 
			
		||||
				if ctrl:
 | 
			
		||||
					ctrl.session = gajim.connections[account].make_new_session(str(jid))
 | 
			
		||||
					new_sess = gajim.connections[account].make_new_session(str(jid))
 | 
			
		||||
					ctrl.set_session(new_sess)
 | 
			
		||||
 | 
			
		||||
				return
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -2044,7 +2045,8 @@ class Interface:
 | 
			
		|||
				contact = gajim.contacts.get_contact(account, str(jid), resource)
 | 
			
		||||
				if not contact:
 | 
			
		||||
					connection = gajim.connections[account]
 | 
			
		||||
					contact = gajim.contacts.create_contact(jid = jid.getStripped(), resource = resource, show = connection.get_status())
 | 
			
		||||
					contact = gajim.contacts.create_contact(jid = jid.getStripped(), 
 | 
			
		||||
							resource = resource, show = connection.get_status())
 | 
			
		||||
				self.roster.new_chat(contact, account, resource = resource)
 | 
			
		||||
 | 
			
		||||
				ctrl = gajim.interface.msg_win_mgr.get_control(str(jid), account)
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -117,15 +117,26 @@ class MessageControl:
 | 
			
		|||
		return len(gajim.events.get_events(self.account, self.contact.jid))
 | 
			
		||||
 | 
			
		||||
	def set_session(self, session):
 | 
			
		||||
		if session == self.session:
 | 
			
		||||
		if hasattr(self, 'session') and session == self.session:
 | 
			
		||||
			return
 | 
			
		||||
 | 
			
		||||
		if self.session:
 | 
			
		||||
		was_encrypted = False
 | 
			
		||||
 | 
			
		||||
		if hasattr(self, 'session') and self.session:
 | 
			
		||||
			if self.session.enable_encryption:
 | 
			
		||||
				was_encrypted = True
 | 
			
		||||
 | 
			
		||||
			print "starting a new session, dropping the old one!"
 | 
			
		||||
			gajim.connections[self.account].delete_session(self.session.jid, self.session.thread_id)
 | 
			
		||||
 | 
			
		||||
		self.session = session
 | 
			
		||||
 | 
			
		||||
		if session:
 | 
			
		||||
			session.control = self
 | 
			
		||||
 | 
			
		||||
			if was_encrypted:
 | 
			
		||||
				self.print_esession_details()
 | 
			
		||||
 | 
			
		||||
	def send_message(self, message, keyID = '', type = 'chat',
 | 
			
		||||
	chatstate = None, msg_id = None, composing_xep = None, resource = None,
 | 
			
		||||
	user_nick = None):
 | 
			
		||||
| 
						 | 
				
			
			@ -134,7 +145,10 @@ class MessageControl:
 | 
			
		|||
		jid = self.contact.jid
 | 
			
		||||
 | 
			
		||||
		if not self.session:
 | 
			
		||||
			self.session = gajim.connections[self.account].make_new_session(self.contact.get_full_jid())
 | 
			
		||||
			fjid = self.contact.get_full_jid()
 | 
			
		||||
			new_session = gajim.connections[self.account].make_new_session(fjid)
 | 
			
		||||
 | 
			
		||||
			self.set_session(new_session)
 | 
			
		||||
 | 
			
		||||
		# Send and update history
 | 
			
		||||
		return gajim.connections[self.account].send_message(jid, message, keyID,
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
	Add table
		
		Reference in a new issue