disable logs in encrypted sessions.
This commit is contained in:
		
							parent
							
								
									4bd805cf07
								
							
						
					
					
						commit
						6fe668d863
					
				
					 6 changed files with 256 additions and 64 deletions
				
			
		| 
						 | 
					@ -1948,10 +1948,13 @@ class ChatControl(ChatControlBase):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	def _on_toggle_e2e_menuitem_activate(self, widget):
 | 
						def _on_toggle_e2e_menuitem_activate(self, widget):
 | 
				
			||||||
		if self.session.enable_encryption:
 | 
							if self.session.enable_encryption:
 | 
				
			||||||
			self.session.enable_encryption = False
 | 
					 | 
				
			||||||
			self.session.terminate_e2e()
 | 
								self.session.terminate_e2e()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								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)
 | 
				
			||||||
		else:
 | 
							else:
 | 
				
			||||||
			self.session.enable_encryption = True
 | 
					 | 
				
			||||||
			self.session.negotiate_e2e()
 | 
								self.session.negotiate_e2e()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	def got_connected(self):
 | 
						def got_connected(self):
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -921,10 +921,7 @@ class Connection(ConnectionHandlers):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		self.connection.send(msg_iq)
 | 
							self.connection.send(msg_iq)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		no_log_for = gajim.config.get_per('accounts', self.name, 'no_log_for')\
 | 
							if session.is_loggable():
 | 
				
			||||||
			.split()
 | 
					 | 
				
			||||||
		ji = gajim.get_jid_without_resource(jid)
 | 
					 | 
				
			||||||
		if self.name not in no_log_for and ji not in no_log_for:
 | 
					 | 
				
			||||||
			log_msg = msg
 | 
								log_msg = msg
 | 
				
			||||||
			if subject:
 | 
								if subject:
 | 
				
			||||||
				log_msg = _('Subject: %s\n%s') % (subject, msg)
 | 
									log_msg = _('Subject: %s\n%s') % (subject, msg)
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1461,11 +1461,6 @@ class ConnectionHandlers(ConnectionVcard, ConnectionBytestream, ConnectionDisco,
 | 
				
			||||||
		tim = time.strptime(tim, '%Y%m%dT%H:%M:%S')
 | 
							tim = time.strptime(tim, '%Y%m%dT%H:%M:%S')
 | 
				
			||||||
		tim = time.localtime(timegm(tim))
 | 
							tim = time.localtime(timegm(tim))
 | 
				
			||||||
		jid = helpers.get_jid_from_iq(msg)
 | 
							jid = helpers.get_jid_from_iq(msg)
 | 
				
			||||||
		no_log_for = gajim.config.get_per('accounts', self.name,
 | 
					 | 
				
			||||||
			'no_log_for')
 | 
					 | 
				
			||||||
		if not no_log_for:
 | 
					 | 
				
			||||||
			no_log_for = ''
 | 
					 | 
				
			||||||
		no_log_for = no_log_for.split()
 | 
					 | 
				
			||||||
		encrypted = False
 | 
							encrypted = False
 | 
				
			||||||
		chatstate = None
 | 
							chatstate = None
 | 
				
			||||||
		encTag = msg.getTag('x', namespace = common.xmpp.NS_ENCRYPTED)
 | 
							encTag = msg.getTag('x', namespace = common.xmpp.NS_ENCRYPTED)
 | 
				
			||||||
| 
						 | 
					@ -1525,7 +1520,7 @@ class ConnectionHandlers(ConnectionVcard, ConnectionBytestream, ConnectionDisco,
 | 
				
			||||||
			if not error_msg:
 | 
								if not error_msg:
 | 
				
			||||||
				error_msg = msgtxt
 | 
									error_msg = msgtxt
 | 
				
			||||||
				msgtxt = None
 | 
									msgtxt = None
 | 
				
			||||||
			if self.name not in no_log_for:
 | 
								if session.is_loggable():
 | 
				
			||||||
				gajim.logger.write('error', frm, error_msg, tim = tim,
 | 
									gajim.logger.write('error', frm, error_msg, tim = tim,
 | 
				
			||||||
					subject = subject)
 | 
										subject = subject)
 | 
				
			||||||
			self.dispatch('MSGERROR', (frm, msg.getErrorCode(), error_msg, msgtxt,
 | 
								self.dispatch('MSGERROR', (frm, msg.getErrorCode(), error_msg, msgtxt,
 | 
				
			||||||
| 
						 | 
					@ -1544,15 +1539,14 @@ class ConnectionHandlers(ConnectionVcard, ConnectionBytestream, ConnectionDisco,
 | 
				
			||||||
				if not self.last_history_line.has_key(jid):
 | 
									if not self.last_history_line.has_key(jid):
 | 
				
			||||||
					return
 | 
										return
 | 
				
			||||||
				self.dispatch('GC_MSG', (frm, msgtxt, tim, has_timestamp, msghtml))
 | 
									self.dispatch('GC_MSG', (frm, msgtxt, tim, has_timestamp, msghtml))
 | 
				
			||||||
				if self.name not in no_log_for and not int(float(time.mktime(tim)))\
 | 
									if session.is_loggable() and not int(float(time.mktime(tim)))\
 | 
				
			||||||
				<= self.last_history_line[jid] and msgtxt:
 | 
									<= self.last_history_line[jid] and msgtxt:
 | 
				
			||||||
					gajim.logger.write('gc_msg', frm, msgtxt, tim = tim)
 | 
										gajim.logger.write('gc_msg', frm, msgtxt, tim = tim)
 | 
				
			||||||
			return
 | 
								return
 | 
				
			||||||
		elif mtype == 'chat': # it's type 'chat'
 | 
							elif mtype == 'chat': # it's type 'chat'
 | 
				
			||||||
			if not msg.getTag('body') and chatstate is None: #no <body>
 | 
								if not msg.getTag('body') and chatstate is None: #no <body>
 | 
				
			||||||
				return
 | 
									return
 | 
				
			||||||
			if msg.getTag('body') and self.name not in no_log_for and jid not in\
 | 
								if msg.getTag('body') and session.is_loggable() and msgtxt:
 | 
				
			||||||
				no_log_for and msgtxt:
 | 
					 | 
				
			||||||
				msg_id = gajim.logger.write('chat_msg_recv', frm, msgtxt, tim = tim,
 | 
									msg_id = gajim.logger.write('chat_msg_recv', frm, msgtxt, tim = tim,
 | 
				
			||||||
					subject = subject)
 | 
										subject = subject)
 | 
				
			||||||
		else: # it's single message
 | 
							else: # it's single message
 | 
				
			||||||
| 
						 | 
					@ -1564,7 +1558,7 @@ class ConnectionHandlers(ConnectionVcard, ConnectionBytestream, ConnectionDisco,
 | 
				
			||||||
				password = invite.getTagData('password')
 | 
									password = invite.getTagData('password')
 | 
				
			||||||
				self.dispatch('GC_INVITATION',(frm, jid_from, reason, password))
 | 
									self.dispatch('GC_INVITATION',(frm, jid_from, reason, password))
 | 
				
			||||||
				return
 | 
									return
 | 
				
			||||||
			if self.name not in no_log_for and jid not in no_log_for and msgtxt:
 | 
								if session.is_loggable()and msgtxt:
 | 
				
			||||||
				gajim.logger.write('single_msg_recv', frm, msgtxt, tim = tim,
 | 
									gajim.logger.write('single_msg_recv', frm, msgtxt, tim = tim,
 | 
				
			||||||
					subject = subject)
 | 
										subject = subject)
 | 
				
			||||||
			mtype = 'normal'
 | 
								mtype = 'normal'
 | 
				
			||||||
| 
						 | 
					@ -1576,7 +1570,7 @@ class ConnectionHandlers(ConnectionVcard, ConnectionBytestream, ConnectionDisco,
 | 
				
			||||||
	# END messageCB
 | 
						# END messageCB
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	def get_session(self, jid, thread_id, type):
 | 
						def get_session(self, jid, thread_id, type):
 | 
				
			||||||
		'''returns an existing session between this connection and 'jid' or starts a new one.'''
 | 
							'''returns an existing session between this connection and 'jid', returns a new one if none exist.'''
 | 
				
			||||||
		session = self.find_session(jid, thread_id, type)
 | 
							session = self.find_session(jid, thread_id, type)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		if session:
 | 
							if session:
 | 
				
			||||||
| 
						 | 
					@ -1588,6 +1582,7 @@ class ConnectionHandlers(ConnectionVcard, ConnectionBytestream, ConnectionDisco,
 | 
				
			||||||
			if bare_jid != jid:
 | 
								if bare_jid != jid:
 | 
				
			||||||
				session = self.find_session(bare_jid, thread_id, type)
 | 
									session = self.find_session(bare_jid, thread_id, type)
 | 
				
			||||||
				if session:
 | 
									if session:
 | 
				
			||||||
 | 
										print repr(bare_jid), repr(thread_id), repr(jid.split("/")[1])
 | 
				
			||||||
					self.move_session(bare_jid, thread_id, jid.split("/")[1])
 | 
										self.move_session(bare_jid, thread_id, jid.split("/")[1])
 | 
				
			||||||
					return session
 | 
										return session
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1609,6 +1604,7 @@ class ConnectionHandlers(ConnectionVcard, ConnectionBytestream, ConnectionDisco,
 | 
				
			||||||
			del self.sessions[jid]
 | 
								del self.sessions[jid]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	def move_session(self, original_jid, thread_id, to_resource):
 | 
						def move_session(self, original_jid, thread_id, to_resource):
 | 
				
			||||||
 | 
							'''moves a session to another resource.'''
 | 
				
			||||||
		session = self.sessions[original_jid][thread_id]
 | 
							session = self.sessions[original_jid][thread_id]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		del self.sessions[original_jid][thread_id]
 | 
							del self.sessions[original_jid][thread_id]
 | 
				
			||||||
| 
						 | 
					@ -1622,13 +1618,15 @@ class ConnectionHandlers(ConnectionVcard, ConnectionBytestream, ConnectionDisco,
 | 
				
			||||||
		self.sessions[new_jid][thread_id] = session
 | 
							self.sessions[new_jid][thread_id] = session
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	def find_null_session(self, jid):
 | 
						def find_null_session(self, jid):
 | 
				
			||||||
		'''returns the session between this connecting and 'jid' that we last sent a message in.
 | 
							'''finds all of the sessions between us and jid that jid hasn't sent a thread_id in yet.
 | 
				
			||||||
this is needed to handle clients that don't support threads; see XEP-0201.'''
 | 
					 | 
				
			||||||
		all = self.sessions[jid].values()
 | 
					 | 
				
			||||||
		null_sessions = filter(lambda s: not s.received_thread_id, all)
 | 
					 | 
				
			||||||
		null_sessions.sort(key=lambda s: s.last_send)
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
		return null_sessions[-1]
 | 
					returns the session that we last sent a message to.'''
 | 
				
			||||||
 | 
							
 | 
				
			||||||
 | 
							sessions_with_jid = self.sessions[jid].values()
 | 
				
			||||||
 | 
							no_threadid_sessions = filter(lambda s: not s.received_thread_id, sessions_with_jid)
 | 
				
			||||||
 | 
							no_threadid_sessions.sort(key=lambda s: s.last_send)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							return no_threadid_sessions[-1]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	def make_new_session(self, jid, thread_id = None, type = 'chat'):
 | 
						def make_new_session(self, jid, thread_id = None, type = 'chat'):
 | 
				
			||||||
		sess = EncryptedStanzaSession(self, jid, thread_id, type)
 | 
							sess = EncryptedStanzaSession(self, jid, thread_id, type)
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -41,7 +41,7 @@ class StanzaSession(object):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		self.last_send = 0
 | 
							self.last_send = 0
 | 
				
			||||||
		self.status = None
 | 
							self.status = None
 | 
				
			||||||
		self.features = {}
 | 
							self.negotiated = {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	def generate_thread_id(self):
 | 
						def generate_thread_id(self):
 | 
				
			||||||
		return "".join([random.choice(string.letters) for x in xrange(0,32)])
 | 
							return "".join([random.choice(string.letters) for x in xrange(0,32)])
 | 
				
			||||||
| 
						 | 
					@ -55,6 +55,29 @@ class StanzaSession(object):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		self.last_send = time.time()
 | 
							self.last_send = time.time()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						def reject_negotiation(self, body = None):
 | 
				
			||||||
 | 
							msg = xmpp.Message()
 | 
				
			||||||
 | 
							feature = msg.NT.feature
 | 
				
			||||||
 | 
							feature.setNamespace(xmpp.NS_FEATURE)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							x = xmpp.DataForm(typ='submit')
 | 
				
			||||||
 | 
							x.addChild(node=xmpp.DataField(name='FORM_TYPE', value='urn:xmpp:ssn'))
 | 
				
			||||||
 | 
							x.addChild(node=xmpp.DataField(name='accept', value='0'))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							feature.addChild(node=x)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if body:
 | 
				
			||||||
 | 
								msg.setBody(body)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							self.send(msg)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							self.cancelled_negotiation()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						def cancelled_negotiation(self):
 | 
				
			||||||
 | 
							'''A negotiation has been cancelled, so reset this session to its default state.'''
 | 
				
			||||||
 | 
							self.status = None
 | 
				
			||||||
 | 
							self.negotiated = {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	def terminate(self):
 | 
						def terminate(self):
 | 
				
			||||||
		msg = xmpp.Message()
 | 
							msg = xmpp.Message()
 | 
				
			||||||
		feature = msg.NT.feature
 | 
							feature = msg.NT.feature
 | 
				
			||||||
| 
						 | 
					@ -101,6 +124,8 @@ class EncryptedStanzaSession(StanzaSession):
 | 
				
			||||||
	def __init__(self, conn, jid, thread_id, type = 'chat'):
 | 
						def __init__(self, conn, jid, thread_id, type = 'chat'):
 | 
				
			||||||
		StanzaSession.__init__(self, conn, jid, thread_id, type = 'chat')
 | 
							StanzaSession.__init__(self, conn, jid, thread_id, type = 'chat')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							self.loggable = True
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		self.xes = {}
 | 
							self.xes = {}
 | 
				
			||||||
		self.es = {}
 | 
							self.es = {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -195,12 +220,24 @@ class EncryptedStanzaSession(StanzaSession):
 | 
				
			||||||
	def hmac(self, key, content):
 | 
						def hmac(self, key, content):
 | 
				
			||||||
		return HMAC.new(key, content, self.hash_alg).digest()
 | 
							return HMAC.new(key, content, self.hash_alg).digest()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	# this should be more generic?
 | 
					 | 
				
			||||||
	def sha256(self, string):
 | 
						def sha256(self, string):
 | 
				
			||||||
		sh = SHA256.new()
 | 
							sh = SHA256.new()
 | 
				
			||||||
		sh.update(string)
 | 
							sh.update(string)
 | 
				
			||||||
		return sh.digest()
 | 
							return sh.digest()
 | 
				
			||||||
	
 | 
						
 | 
				
			||||||
 | 
						base28_chr = "acdefghikmopqruvwxy123456789"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						def sas_28x5(self, m_a, form_b):
 | 
				
			||||||
 | 
							sha = self.sha256(m_a + form_b + 'Short Authentication String')
 | 
				
			||||||
 | 
							lsb24 = self.decode_mpi(sha[-3:])
 | 
				
			||||||
 | 
							return self.base28(lsb24)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						def base28(self, n):
 | 
				
			||||||
 | 
							if n >= 28:
 | 
				
			||||||
 | 
								return self.base28(n / 28) + self.base28_chr[n % 28]
 | 
				
			||||||
 | 
							else:
 | 
				
			||||||
 | 
								return self.base28_chr[n]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	def generate_initiator_keys(self, k):
 | 
						def generate_initiator_keys(self, k):
 | 
				
			||||||
		return (self.hmac(k, 'Initiator Cipher Key'),
 | 
							return (self.hmac(k, 'Initiator Cipher Key'),
 | 
				
			||||||
						self.hmac(k, 'Initiator MAC Key'),
 | 
											self.hmac(k, 'Initiator MAC Key'),
 | 
				
			||||||
| 
						 | 
					@ -265,7 +302,15 @@ class EncryptedStanzaSession(StanzaSession):
 | 
				
			||||||
	def decrypt(self, ciphertext):
 | 
						def decrypt(self, ciphertext):
 | 
				
			||||||
		return self.decrypter.decrypt(ciphertext)
 | 
							return self.decrypter.decrypt(ciphertext)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						def logging_preference(self):
 | 
				
			||||||
 | 
							if gajim.config.get('log_encrypted_sessions'):
 | 
				
			||||||
 | 
								return ["may", "mustnot"]
 | 
				
			||||||
 | 
							else:
 | 
				
			||||||
 | 
								return ["mustnot", "may"]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	def negotiate_e2e(self):
 | 
						def negotiate_e2e(self):
 | 
				
			||||||
 | 
							self.negotiated = {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		request = xmpp.Message()
 | 
							request = xmpp.Message()
 | 
				
			||||||
		feature = request.NT.feature
 | 
							feature = request.NT.feature
 | 
				
			||||||
		feature.setNamespace(xmpp.NS_FEATURE)
 | 
							feature.setNamespace(xmpp.NS_FEATURE)
 | 
				
			||||||
| 
						 | 
					@ -276,8 +321,7 @@ class EncryptedStanzaSession(StanzaSession):
 | 
				
			||||||
		x.addChild(node=xmpp.DataField(name='accept', value='1', typ='boolean', required=True))
 | 
							x.addChild(node=xmpp.DataField(name='accept', value='1', typ='boolean', required=True))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		# this field is incorrectly called 'otr' in XEPs 0116 and 0217
 | 
							# this field is incorrectly called 'otr' in XEPs 0116 and 0217
 | 
				
			||||||
		# unsupported options: 'mustnot'
 | 
							x.addChild(node=xmpp.DataField(name='logging', typ='list-single', options=self.logging_preference(), required=True))
 | 
				
			||||||
		x.addChild(node=xmpp.DataField(name='logging', typ='list-single', options=['may'], required=True))
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
		# unsupported options: 'disabled', 'enabled'
 | 
							# unsupported options: 'disabled', 'enabled'
 | 
				
			||||||
		x.addChild(node=xmpp.DataField(name='disclosure', typ='list-single', options=['never'], required=True))
 | 
							x.addChild(node=xmpp.DataField(name='disclosure', typ='list-single', options=['never'], required=True))
 | 
				
			||||||
| 
						 | 
					@ -317,12 +361,10 @@ class EncryptedStanzaSession(StanzaSession):
 | 
				
			||||||
		self.send(request)
 | 
							self.send(request)
 | 
				
			||||||
	
 | 
						
 | 
				
			||||||
	# 4.3 esession response (bob)
 | 
						# 4.3 esession response (bob)
 | 
				
			||||||
	def respond_e2e_bob(self, request_form):
 | 
						def verify_options_bob(self, form):
 | 
				
			||||||
		response = xmpp.Message()
 | 
							negotiated = {}
 | 
				
			||||||
		feature = response.NT.feature
 | 
							not_acceptable = []
 | 
				
			||||||
		feature.setNamespace(xmpp.NS_FEATURE)
 | 
							ask_user = {}
 | 
				
			||||||
 | 
					 | 
				
			||||||
		x = xmpp.DataForm(typ='submit')
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
		fixed = { 'disclosure': 'never',
 | 
							fixed = { 'disclosure': 'never',
 | 
				
			||||||
								'security': 'e2e',
 | 
													'security': 'e2e',
 | 
				
			||||||
| 
						 | 
					@ -333,21 +375,16 @@ class EncryptedStanzaSession(StanzaSession):
 | 
				
			||||||
						'init_pubkey': 'none',
 | 
											'init_pubkey': 'none',
 | 
				
			||||||
						'resp_pubkey': 'none',
 | 
											'resp_pubkey': 'none',
 | 
				
			||||||
										'ver': '1.0',
 | 
															'ver': '1.0',
 | 
				
			||||||
								'sas_algs': 'sas28x5',
 | 
													'sas_algs': 'sas28x5' }
 | 
				
			||||||
								'logging': 'may'			}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		not_acceptable = []
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
		self.encryptable_stanzas = ['message']
 | 
							self.encryptable_stanzas = ['message']
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		self.sas_algs = 'sas28x5'
 | 
							self.sas_algs = 'sas28x5'
 | 
				
			||||||
		self.cipher = AES
 | 
							self.cipher = AES
 | 
				
			||||||
		self.hash_alg = SHA256
 | 
							self.hash_alg = SHA256
 | 
				
			||||||
		self.compression = None
 | 
							self.compression = None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		x.addChild(node=xmpp.DataField(name='FORM_TYPE', value='urn:xmpp:ssn'))
 | 
							for name, field in map(lambda name: (name, form.getField(name)), form.asDict().keys()):
 | 
				
			||||||
		x.addChild(node=xmpp.DataField(name='accept', value='true'))
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		for name, field in map(lambda name: (name, request_form.getField(name)), request_form.asDict().keys()):
 | 
					 | 
				
			||||||
			options = map(lambda x: x[1], field.getOptions())
 | 
								options = map(lambda x: x[1], field.getOptions())
 | 
				
			||||||
			values = field.getValues()
 | 
								values = field.getValues()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -356,28 +393,61 @@ class EncryptedStanzaSession(StanzaSession):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			if name in fixed:
 | 
								if name in fixed:
 | 
				
			||||||
				if fixed[name] in options:
 | 
									if fixed[name] in options:
 | 
				
			||||||
					x.addChild(node=xmpp.DataField(name=name, value=fixed[name]))
 | 
										negotiated[name] = fixed[name]
 | 
				
			||||||
				else:
 | 
									else:
 | 
				
			||||||
					not_acceptable.append(name)
 | 
										not_acceptable.append(name)
 | 
				
			||||||
			elif name == 'modp':
 | 
					 | 
				
			||||||
				# the offset of the group we chose (need it to match up with the dhhash)
 | 
					 | 
				
			||||||
				group_order = 0
 | 
					 | 
				
			||||||
				self.modp = int(options[group_order])
 | 
					 | 
				
			||||||
				x.addChild(node=xmpp.DataField(name='modp', value=self.modp))
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
				g = dh.generators[self.modp]
 | 
					 | 
				
			||||||
				p = dh.primes[self.modp]
 | 
					 | 
				
			||||||
			elif name == 'rekey_freq':
 | 
								elif name == 'rekey_freq':
 | 
				
			||||||
				preferred = int(options[0])
 | 
									preferred = int(options[0])
 | 
				
			||||||
				x.addChild(node=xmpp.DataField(name='rekey_freq', value=preferred))
 | 
									negotiated['rekey_freq'] = preferred
 | 
				
			||||||
 | 
					 | 
				
			||||||
				self.rekey_freq = preferred
 | 
									self.rekey_freq = preferred
 | 
				
			||||||
			elif name == 'my_nonce':
 | 
								elif name == 'logging':
 | 
				
			||||||
				self.n_o = base64.b64decode(field.getValue())
 | 
									my_prefs = self.logging_preference()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		# XXX do something with not_acceptable
 | 
									if my_prefs[0] in options:
 | 
				
			||||||
 | 
										pref = my_prefs[0]
 | 
				
			||||||
 | 
										negotiated['logging'] = pref
 | 
				
			||||||
 | 
									else:
 | 
				
			||||||
 | 
										for pref in my_prefs:
 | 
				
			||||||
 | 
											if pref in options:
 | 
				
			||||||
 | 
												ask_user['logging'] = pref
 | 
				
			||||||
 | 
												break
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		self.He = request_form.getField('dhhashes').getValues()[group_order].encode("utf8")
 | 
										if not 'logging' in ask_user:
 | 
				
			||||||
 | 
											not_acceptable.append(name)
 | 
				
			||||||
 | 
								else:
 | 
				
			||||||
 | 
									# some things are handled elsewhere, some things are not-implemented
 | 
				
			||||||
 | 
									pass
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							return (negotiated, not_acceptable, ask_user)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						# 4.3 esession response (bob)
 | 
				
			||||||
 | 
						def respond_e2e_bob(self, form, negotiated, not_acceptable):
 | 
				
			||||||
 | 
							response = xmpp.Message()
 | 
				
			||||||
 | 
							feature = response.NT.feature
 | 
				
			||||||
 | 
							feature.setNamespace(xmpp.NS_FEATURE)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							x = xmpp.DataForm(typ='submit')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							x.addChild(node=xmpp.DataField(name='FORM_TYPE', value='urn:xmpp:ssn'))
 | 
				
			||||||
 | 
							x.addChild(node=xmpp.DataField(name='accept', value='true'))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							for name in negotiated:
 | 
				
			||||||
 | 
								x.addChild(node=xmpp.DataField(name=name, value=negotiated[name]))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							self.negotiated = negotiated
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							# the offset of the group we chose (need it to match up with the dhhash)
 | 
				
			||||||
 | 
							group_order = 0
 | 
				
			||||||
 | 
							self.modp = int(form.getField('modp').getOptions()[group_order][1])
 | 
				
			||||||
 | 
							x.addChild(node=xmpp.DataField(name='modp', value=self.modp))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							g = dh.generators[self.modp]
 | 
				
			||||||
 | 
							p = dh.primes[self.modp]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							self.n_o = base64.b64decode(form['my_nonce'])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							dhhashes = form.getField('dhhashes').getValues()
 | 
				
			||||||
 | 
							self.He = dhhashes[group_order].encode("utf8")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		bytes = int(self.n / 8)
 | 
							bytes = int(self.n / 8)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -389,30 +459,61 @@ class EncryptedStanzaSession(StanzaSession):
 | 
				
			||||||
		self.y = self.srand(2 ** (2 * self.n - 1), p - 1)
 | 
							self.y = self.srand(2 ** (2 * self.n - 1), p - 1)
 | 
				
			||||||
		self.d = self.powmod(g, self.y, p)
 | 
							self.d = self.powmod(g, self.y, p)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		to_add = { 'my_nonce': self.n_s, 'dhkeys': self.encode_mpi(self.d), 'counter': self.encode_mpi(self.c_o), 'nonce': self.n_o }
 | 
							to_add = { 'my_nonce': self.n_s,
 | 
				
			||||||
 | 
												 'dhkeys': self.encode_mpi(self.d), 
 | 
				
			||||||
 | 
												 'counter': self.encode_mpi(self.c_o), 
 | 
				
			||||||
 | 
												 'nonce': self.n_o }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		for name in to_add:
 | 
							for name in to_add:
 | 
				
			||||||
			b64ed = base64.b64encode(to_add[name])
 | 
								b64ed = base64.b64encode(to_add[name])
 | 
				
			||||||
			x.addChild(node=xmpp.DataField(name=name, value=b64ed))
 | 
								x.addChild(node=xmpp.DataField(name=name, value=b64ed))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		self.form_a = ''.join(map(lambda el: xmpp.c14n.c14n(el), request_form.getChildren()))
 | 
							self.form_a = ''.join(map(lambda el: xmpp.c14n.c14n(el), form.getChildren()))
 | 
				
			||||||
		self.form_b = ''.join(map(lambda el: xmpp.c14n.c14n(el), x.getChildren()))
 | 
							self.form_b = ''.join(map(lambda el: xmpp.c14n.c14n(el), x.getChildren()))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		self.status = 'responded-e2e'
 | 
							self.status = 'responded-e2e'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		feature.addChild(node=x)
 | 
							feature.addChild(node=x)
 | 
				
			||||||
 | 
							
 | 
				
			||||||
 | 
							if not_acceptable:
 | 
				
			||||||
 | 
								pass
 | 
				
			||||||
 | 
					# XXX
 | 
				
			||||||
 | 
					#  <error code='406' type='modify'>
 | 
				
			||||||
 | 
					#    <not-acceptable xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/>
 | 
				
			||||||
 | 
					#    <feature xmlns='http://jabber.org/protocol/feature-neg'>
 | 
				
			||||||
 | 
					#      <field var='security'/>
 | 
				
			||||||
 | 
					#    </feature>
 | 
				
			||||||
 | 
					#  </error>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		self.send(response)
 | 
							self.send(response)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	# 'Alice Accepts'
 | 
						# 'Alice Accepts'
 | 
				
			||||||
	def accept_e2e_alice(self, form):
 | 
						def verify_options_alice(self, form):
 | 
				
			||||||
#		1.	Verify that the ESession options selected by Bob are acceptable
 | 
					#		1.	Verify that the ESession options selected by Bob are acceptable
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							negotiated = {}
 | 
				
			||||||
 | 
							ask_user = {}
 | 
				
			||||||
 | 
							not_acceptable = []
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if not form['logging'] in self.logging_preference():
 | 
				
			||||||
 | 
								not_acceptable.append(form['logging'])
 | 
				
			||||||
 | 
							elif form['logging'] != self.logging_preference()[0]:
 | 
				
			||||||
 | 
								ask_user['logging'] = form['logging']
 | 
				
			||||||
 | 
							else:
 | 
				
			||||||
 | 
								negotiated['logging'] = self.logging_preference()[0]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							return (negotiated, not_acceptable, ask_user)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						# 'Alice Accepts', continued
 | 
				
			||||||
 | 
						def accept_e2e_alice(self, form, negotiated):
 | 
				
			||||||
		self.encryptable_stanzas = ['message']
 | 
							self.encryptable_stanzas = ['message']
 | 
				
			||||||
		self.sas_algs = 'sas28x5'
 | 
							self.sas_algs = 'sas28x5'
 | 
				
			||||||
		self.cipher = AES
 | 
							self.cipher = AES
 | 
				
			||||||
		self.hash_alg = SHA256
 | 
							self.hash_alg = SHA256
 | 
				
			||||||
		self.compression = None
 | 
							self.compression = None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							self.negotiated = negotiated
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#		2.	Return a <not-acceptable/> error to Bob unless: 1 < d < p - 1
 | 
					#		2.	Return a <not-acceptable/> error to Bob unless: 1 < d < p - 1
 | 
				
			||||||
		self.form_b = ''.join(map(lambda el: xmpp.c14n.c14n(el), form.getChildren()))
 | 
							self.form_b = ''.join(map(lambda el: xmpp.c14n.c14n(el), form.getChildren()))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -457,6 +558,11 @@ class EncryptedStanzaSession(StanzaSession):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		m_a = self.hmac(self.km_s, self.encode_mpi(old_c_s) + id_a)
 | 
							m_a = self.hmac(self.km_s, self.encode_mpi(old_c_s) + id_a)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							# check for a retained secret
 | 
				
			||||||
 | 
							# if none exists, prompt the user with the SAS
 | 
				
			||||||
 | 
							if self.sas_algs == 'sas28x5':
 | 
				
			||||||
 | 
								print "sas: %s" % self.sas_28x5(m_a, self.form_b)
 | 
				
			||||||
 | 
								
 | 
				
			||||||
		result.addChild(node=xmpp.DataField(name='identity', value=base64.b64encode(id_a)))
 | 
							result.addChild(node=xmpp.DataField(name='identity', value=base64.b64encode(id_a)))
 | 
				
			||||||
		result.addChild(node=xmpp.DataField(name='mac', value=base64.b64encode(m_a)))
 | 
							result.addChild(node=xmpp.DataField(name='mac', value=base64.b64encode(m_a)))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -520,6 +626,11 @@ class EncryptedStanzaSession(StanzaSession):
 | 
				
			||||||
		self.srs = ''
 | 
							self.srs = ''
 | 
				
			||||||
		oss = ''
 | 
							oss = ''
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							# check for a retained secret
 | 
				
			||||||
 | 
							# if none exists, prompt the user with the SAS
 | 
				
			||||||
 | 
							if self.sas_algs == 'sas28x5':
 | 
				
			||||||
 | 
								print "sas: %s" % self.sas_28x5(m_a, self.form_b)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		k = self.sha256(k + self.srs + oss)
 | 
							k = self.sha256(k + self.srs + oss)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		# XXX I can skip generating ks_o here
 | 
							# XXX I can skip generating ks_o here
 | 
				
			||||||
| 
						 | 
					@ -556,6 +667,10 @@ class EncryptedStanzaSession(StanzaSession):
 | 
				
			||||||
		self.srs = self.hmac(k, 'New Retained Secret')
 | 
							self.srs = self.hmac(k, 'New Retained Secret')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		# destroy k
 | 
							# destroy k
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if self.negotiated['logging'] == 'mustnot':
 | 
				
			||||||
 | 
								self.loggable = False
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		self.status = 'active'
 | 
							self.status = 'active'
 | 
				
			||||||
		self.enable_encryption = True
 | 
							self.enable_encryption = True
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -594,6 +709,9 @@ class EncryptedStanzaSession(StanzaSession):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# Note: If Alice discovers an error then she SHOULD ignore any encrypted content she received in the stanza.
 | 
					# Note: If Alice discovers an error then she SHOULD ignore any encrypted content she received in the stanza.
 | 
				
			||||||
		# XXX check for MAC equality?
 | 
							# XXX check for MAC equality?
 | 
				
			||||||
 | 
						
 | 
				
			||||||
 | 
							if self.negotiated['logging'] == 'mustnot':
 | 
				
			||||||
 | 
								self.loggable = False
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		self.status = 'active'
 | 
							self.status = 'active'
 | 
				
			||||||
		self.enable_encryption = True
 | 
							self.enable_encryption = True
 | 
				
			||||||
| 
						 | 
					@ -647,3 +765,14 @@ class EncryptedStanzaSession(StanzaSession):
 | 
				
			||||||
		StanzaSession.acknowledge_termination(self)
 | 
							StanzaSession.acknowledge_termination(self)
 | 
				
			||||||
		
 | 
							
 | 
				
			||||||
		self.enable_encryption = False
 | 
							self.enable_encryption = False
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						def is_loggable(self):
 | 
				
			||||||
 | 
							name = self.conn.name
 | 
				
			||||||
 | 
							no_log_for = gajim.config.get_per('accounts', name, 'no_log_for')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if not no_log_for:
 | 
				
			||||||
 | 
								no_log_for = ''
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							no_log_for = no_log_for.split()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							return self.loggable and name not in no_log_for and self.jid not in no_log_for
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										73
									
								
								src/gajim.py
									
										
									
									
									
								
							
							
						
						
									
										73
									
								
								src/gajim.py
									
										
									
									
									
								
							| 
						 | 
					@ -1657,13 +1657,64 @@ class Interface:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	def handle_session_negotiation(self, account, data):
 | 
						def handle_session_negotiation(self, account, data):
 | 
				
			||||||
		jid, session, form = data
 | 
							jid, session, form = data
 | 
				
			||||||
		
 | 
						
 | 
				
			||||||
		# encrypted session states
 | 
							if form.getField('accept') and not form['accept'] in ('1', 'true'):
 | 
				
			||||||
		if form.getType() == 'form' and u'e2e' in map(lambda x: x[1], form.getField('security').getOptions()):
 | 
								dialogs.InformationDialog(_('Session negotiation cancelled.'),
 | 
				
			||||||
			session.respond_e2e_bob(form)
 | 
										_('The client at %s cancelled the session negotiation.') % (jid))
 | 
				
			||||||
 | 
								session.cancelled_negotiation()
 | 
				
			||||||
			return
 | 
								return
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							# encrypted session states. these are descriped in stanza_session.py
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							# bob responds
 | 
				
			||||||
 | 
							if form.getType() == 'form' and u'e2e' in map(lambda x: x[1], form.getField('security').getOptions()):
 | 
				
			||||||
 | 
								negotiated, not_acceptable, ask_user = session.verify_options_bob(form)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								if ask_user:
 | 
				
			||||||
 | 
									def accept_nondefault_options(widget):
 | 
				
			||||||
 | 
										negotiated.update(ask_user)
 | 
				
			||||||
 | 
										session.respond_e2e_bob(form, negotiated, not_acceptable)
 | 
				
			||||||
 | 
										
 | 
				
			||||||
 | 
										dialog.destroy()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									def reject_nondefault_options(widget):
 | 
				
			||||||
 | 
										for key in ask_user.keys():
 | 
				
			||||||
 | 
											not_acceptable.append(key)   # XXX for some reason I can't concatenate using += here?
 | 
				
			||||||
 | 
										session.respond_e2e_bob(form, negotiated, not_acceptable)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
										dialog.destroy()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									dialog = dialogs.ConfirmationDialog(_('confirm these negotiation options'),
 | 
				
			||||||
 | 
											_('are the following options acceptable? %s') % (ask_user),
 | 
				
			||||||
 | 
											on_response_ok = accept_nondefault_options,
 | 
				
			||||||
 | 
											on_response_cancel = reject_nondefault_options)
 | 
				
			||||||
 | 
								else:
 | 
				
			||||||
 | 
									session.respond_e2e_bob(form, negotiated, not_acceptable)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								return
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							# alice accepts
 | 
				
			||||||
		elif session.status == 'requested-e2e' and form.getType() == 'submit':
 | 
							elif session.status == 'requested-e2e' and form.getType() == 'submit':
 | 
				
			||||||
			session.accept_e2e_alice(form)
 | 
								negotiated, not_acceptable, ask_user = session.verify_options_alice(form)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								if ask_user:
 | 
				
			||||||
 | 
									def accept_nondefault_options(widget):
 | 
				
			||||||
 | 
										negotiated.update(ask_user)
 | 
				
			||||||
 | 
										session.accept_e2e_alice(form, negotiated)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
										dialog.destroy()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									def reject_nondefault_options(widget):
 | 
				
			||||||
 | 
										session.reject_negotiation()
 | 
				
			||||||
 | 
										dialog.destroy()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									dialog = dialogs.ConfirmationDialog(_('confirm these negotiation options'),
 | 
				
			||||||
 | 
											_('are the following options acceptable? %s') % (ask_user),
 | 
				
			||||||
 | 
											on_response_ok = accept_nondefault_options,
 | 
				
			||||||
 | 
											on_response_cancel = reject_nondefault_options)
 | 
				
			||||||
 | 
								else:
 | 
				
			||||||
 | 
									session.accept_e2e_alice(form, negotiated)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			return
 | 
								return
 | 
				
			||||||
		elif session.status == 'responded-e2e' and form.getType() == 'result':
 | 
							elif session.status == 'responded-e2e' and form.getType() == 'result':
 | 
				
			||||||
			session.accept_e2e_bob(form)
 | 
								session.accept_e2e_bob(form)
 | 
				
			||||||
| 
						 | 
					@ -1671,6 +1722,18 @@ class Interface:
 | 
				
			||||||
		elif session.status == 'identified-alice' and form.getType() == 'result':
 | 
							elif session.status == 'identified-alice' and form.getType() == 'result':
 | 
				
			||||||
			session.final_steps_alice(form)
 | 
								session.final_steps_alice(form)
 | 
				
			||||||
			return
 | 
								return
 | 
				
			||||||
 | 
							
 | 
				
			||||||
 | 
							if form.getField('terminate'):
 | 
				
			||||||
 | 
								if form.getField('terminate').getValue() in ('1', 'true'):
 | 
				
			||||||
 | 
									session.acknowledge_termination()
 | 
				
			||||||
 | 
									gajim.connections[account].delete_session(str(jid), session.thread_id)
 | 
				
			||||||
 | 
								
 | 
				
			||||||
 | 
									ctrl = gajim.interface.msg_win_mgr.get_control(str(jid), account)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									if ctrl:
 | 
				
			||||||
 | 
										ctrl.session = gajim.connections[self.account].make_new_session(str(jid))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									return
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		# non-esession negotiation. this isn't very useful, but i'm keeping it around
 | 
							# non-esession negotiation. this isn't very useful, but i'm keeping it around
 | 
				
			||||||
		# to test my test suite.
 | 
							# to test my test suite.
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -115,6 +115,8 @@ class MessageControl:
 | 
				
			||||||
			return
 | 
								return
 | 
				
			||||||
		if self.session:
 | 
							if self.session:
 | 
				
			||||||
			print "starting a new session, forgetting about the old one!"
 | 
								print "starting a new session, forgetting about the old one!"
 | 
				
			||||||
 | 
								gajim.connections[self.account].delete_session(self.contact.jid, self.session.thread_id)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		self.session = session
 | 
							self.session = session
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	def send_message(self, message, keyID = '', type = 'chat',
 | 
						def send_message(self, message, keyID = '', type = 'chat',
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
	Add table
		
		Reference in a new issue