Exemple #1
0
	def __init__(self, client, my_jid, their_jid, thread, replay=OtrReplay()):
		self.echolalic = False
		self.my_jid = my_jid
		self.their_jid = their_jid
		self.thread = thread
		self.replay = replay
		self.client = client
		self.auth = OtrAuth(self.replay)
Exemple #2
0
class OtrHandler:

	def __init__(self, client, my_jid, their_jid, thread, replay=OtrReplay()):
		self.echolalic = False
		self.my_jid = my_jid
		self.their_jid = their_jid
		self.thread = thread
		self.replay = replay
		self.client = client
		self.auth = OtrAuth(self.replay)
				
	def message_factory(self):
		return OtrMessage(from_jid=self.my_jid, to_jid=self.their_jid, thread=self.thread)
				
	def initiate(self):
		if ( not self.auth.message_state_is("MSGSTATE_PLAINTEXT") or
			 not self.auth.auth_state_is("AUTHSTATE_NONE") ):
			logging.error("Protocol already initiated")
			return
		# send query
		query = self.message_factory()
		query.create_query()
		self.client.send(query.jabber_msg)
	
	def respond_to_plaintext(self, msg):
		if self.auth.message_state_is("MSGSTATE_PLAINTEXT"):
			if OtrOptions["REQUIRE_ENCRYPTION"]:
				logging.warn("Message was received UNENCRYPTED.")
		else:
			logging.warn("Message was received UNENCRYPTED.")
		return msg.jabber_msg # display plaintext to user
		
	# wait
	def respond_to_query(self, msg):
		logging.debug("Responding to Query")
		logging.debug("OTR REQUEST: "+str(msg.versions))
		
		if not msg.versions['2']:
			response = self.message_factory().create_error('Version 1 Not Supported')
			self.client.send(response)
			return None # nothing to show user
		
		# RESPOND WITH DH_COMMIT MESSAGE
		
		my_dh_keyid = self.auth.dh_keys.get_my_cur_keyid()
		self.auth.dh_keys.generate_r_secret()
		self.auth.dh_keys.mark_my_key_as_used(my_dh_keyid)
		enc_gxmpi_data = self.auth.dh_keys.encrypt_my_public_factor_mpi(my_dh_keyid)
		hash_gxmpi_data = self.auth.dh_keys.hash_my_public_factor_mpi(my_dh_keyid)
		
		response = self.message_factory().create_dh_commit(enc_gxmpi_data, hash_gxmpi_data)
		
		self.replay.check('msg_dh_commit', response.jabber_msg.getBody())
		
		self.auth.set_auth_state("AUTHSTATE_AWAITING_DHKEY")
		self.client.send(response.jabber_msg)
		
		return None # nothing to user
		
	def respond_to_whitespaced(self, msg):
		logging.debug("Responding to Whitespaced")
		logging.debug("OTR (secret) REQUEST: "+str(msg.versions))
	
		if self.auth.message_state_is("MSGSTATE_PLAINTEXT"):
			if OtrOptions["REQUIRE_ENCRYPTION"]:
				logging.warn("Message was received UNENCRYPTED.")
		else:
			logging.warn("Message was received UNENCRYPTED.")
		
		# replace the whitespace tags
		body = msg.jabber_msg.getBody()
		body = body.replace(OtrConstants['whitespace_base'], '')
		body = body.replace(OtrConstants['whitespace_v1'], '')
		body = body.replace(OtrConstants['whitespace_v2'], '')
		msg.jabber_msg.setBody(body)
		
		if OtrOptions["WHITESPACE_START_AKE"]:
			if not msg.versions['2']:
				response = self.message_factory().create_error('Version 1 Not Supported')
				self.client.send(response)
				return None # nothing to show user
		
			# RESPOND WITH DH_COMMIT MESSAGE
			
			my_dh_keyid = self.auth.dh_keys.get_my_cur_keyid()
			self.auth.dh_keys.generate_r_secret()
			self.auth.dh_keys.mark_my_key_as_used(my_dh_keyid)
			enc_gxmpi_data = self.auth.dh_keys.encrypt_my_public_factor_mpi(my_dh_keyid)
			hash_gxmpi_data = self.auth.dh_keys.hash_my_public_factor_mpi(my_dh_keyid)		
			
			response = self.message_factory().create_dh_commit(enc_gxmpi_data, hash_gxmpi_data)
		
			self.replay.check('msg_dh_commit', response.jabber_msg.getBody())
		
			self.auth.set_auth_state("AUTHSTATE_AWAITING_DHKEY")
			self.client.send(response.jabber_msg)
		
		return msg.jabber_msg # send user the plaintext message
		
	# initiate	
	def respond_to_dh_commit(self, msg):
		logging.debug("Responding to DH Commit")
		
		send_dh_key = True
		
		if self.auth.auth_state_is("AUTHSTATE_NONE"):
			# Reply with a D-H Key Message, and transition authstate to AUTHSTATE_AWAITING_REVEALSIG.
			pass # pick it up in the 'if' below
			
		elif self.auth.auth_state_is("AUTHSTATE_AWAITING_DHKEY"):
			#This is the trickiest transition in the whole protocol. It indicates that you have
			# already sent a D-H Commit message to your correspondent, but that he either
			#  didn't receive it, or just didn't receive it yet, and has sent you one as well. 
			#  The symmetry will be broken by comparing the hashed gx you sent in your D-H Commit 
			#  Message with the one you received, considered as 32-byte unsigned big-endian values.
			my_dh_keyid = self.auth.dh_keys.get_my_cur_keyid()
			
			my_hashed_gxmpi = self.auth.dh_keys.hash_my_public_factor_mpi(my_dh_keyid)
			their_hashed_gxmpi = _OT.data_to_bytes(msg.hash_gxmpi_data)
			
			my_hash_as_int = _OT.bytes_to_int(my_hashed_gxmpi)
			their_hash_as_int = _OT.bytes_to_int(their_hashed_gxmpi)
		
			if my_hash_as_int > their_hash_as_int:
				# If yours is the higher hash value:
				# Ignore the incoming D-H Commit message, but resend your D-H Commit message.
				self.auth.dh_keys.mark_my_key_as_used(my_dh_keyid)
				enc_gxmpi_data = self.auth.dh_keys.encrypt_my_public_factor_mpi(my_dh_keyid)
				hash_gxmpi_data = self.auth.dh_keys.hash_my_public_factor_mpi(my_dh_keyid)
				response = self.message_factory().create_dh_commit(enc_gxmpi_data, hash_gxmpi_data)
				self.replay.check('msg_dh_commit', response.jabber_msg.getBody())
				send_dh_key = False
				self.client.send(response.jabber_msg)
							
		if send_dh_key:
			my_dh_keyid = self.auth.dh_keys.get_my_cur_keyid()
			self.auth.dh_keys.mark_my_key_as_used(my_dh_keyid)
		
			# this is g**y formatted as an MPI (4 byte length prepended)	
			# our D-H secret is y (with g**x the shared key is g**(xy))
			gympi = self.auth.dh_keys.my_public_factor_to_mpi(my_dh_keyid)
		
			# SAVE their info
			self.auth.dh_keys.store_their_commitment( _OT.data_to_bytes(msg.enc_gxmpi_data),
													  _OT.data_to_bytes(msg.hash_gxmpi_data) )
		
			# ok, now make dh_key message
			response = self.message_factory().create_dh_key(gympi)
			self.replay.check('msg_dh_key', response.jabber_msg.getBody())
			self.auth.set_auth_state("AUTHSTATE_AWAITING_REVEALSIG")
			self.client.send(response.jabber_msg)
				
		return None # nothing for user
	
	# wait
	def respond_to_dh_key(self, msg):
		logging.debug("Responding to DH Key")
		
		send_reveal_sig = True
		
		my_dh_keyid = self.auth.dh_keys.get_my_cur_keyid()
		
		if self.auth.auth_state_is("AUTHSTATE_AWAITING_DHKEY"):
			# calculate the shared dh key
			their_dh_factor = _OT.mpi_to_int(msg.gympi)
			self.auth.dh_keys.store_their_public_factor(their_dh_factor)
			
			# calculate the factor's we'll need
			self.auth.dh_keys.compute_c_and_m_factors()
		
			# load DSA key
			self.auth.dsa_keys.load_my_key()
		
			self.auth.compute_my_M_and_X_values()
			
		elif self.auth.auth_state_is("AUTHSTATE_AWAITING_SIG"):
			if msg.gympi == self.auth.dh_keys.their_public_factor_to_mpi():
				pass # retransmit reveal sig msg
			else:
				send_reveal_sig = False
		else:
			send_reveal_sig = False
			
		if send_reveal_sig:
			revealed_key_data = self.auth.dh_keys.get_r_secret()
			enc_sig_data = self.auth.get_enc_sig()
			sig_mac = self.auth.get_enc_sig_mac()
			response = self.message_factory().create_reveal_sig(revealed_key_data, enc_sig_data, sig_mac)
			self.replay.check('msg_reveal_sig', response.jabber_msg.getBody())
			self.auth.set_auth_state("AUTHSTATE_AWAITING_SIG")
			self.client.send(response.jabber_msg)
		
		return None # nothing for user
	
	# etc	
	def respond_to_reveal_sig(self, msg):
		logging.debug("Responding to Reveal Sig")
		if not self.auth.auth_state_is("AUTHSTATE_AWAITING_REVEALSIG"):
			return None # nothing for user
			
		# Now decrypt and check their DH factor 
		my_dh_keyid = self.auth.dh_keys.get_my_cur_keyid()
		it_checks_out = self.auth.dh_keys.decrypt_their_public_factor( _OT.data_to_bytes(msg.revealed_key_data) )
		
		if not it_checks_out:
			err_msg = self.message_factory()
			err_msg.create_error('Committed DH factor incorrect')
			self.client.send(err_msg.jabber_msg)
			return None # nothing for user
			raise Exception('stop here - committed DH factor incorrect')
			
		# calculate the factors we'll need
		self.auth.dh_keys.compute_c_and_m_factors()
		
		# check their sig
		it_checks_out = self.auth.check_their_sig(_OT.data_to_bytes(msg.enc_sig_data), msg.sig_mac)
		
		if not it_checks_out:
			err_msg = self.message_factory()
			err_msg.create_error('Signature incorrect')
			self.client.send(err_msg.jabber_msg)
			return None # nothing for user
			#raise Exception('stop here - signature incorrect')
		
		# ok, now make our sig message
		# load DSA key
		self.auth.dsa_keys.load_my_key()
				
		self.auth.compute_my_M_and_X_values(usePrimes=True)
		enc_sig_data = self.auth.get_enc_sig()
		sig_mac = self.auth.get_enc_sig_mac(usePrimes=True)
		
		response = self.message_factory().create_signature(enc_sig_data, sig_mac)
		
		self.replay.check('msg_signature', response.jabber_msg.getBody())
		
		self.client.send(response.jabber_msg)
		
		self.auth.set_auth_state("AUTHSTATE_NONE")
		self.auth.set_message_state("MSGSTATE_ENCRYPTED")
		self.auth.dh_keys.reset_session()
		self.auth.authing = False
		self.auth.dh_keys.authing = False
		
		return None # nothing for user
		
	def respond_to_signature(self, msg):
		logging.debug("Responding to Signiature")
		
		if not self.auth.auth_state_is("AUTHSTATE_AWAITING_SIG"):
			return None # nothing for user
		
		# check their sig
		it_checks_out = self.auth.check_their_sig(_OT.data_to_bytes(msg.enc_sig_data), msg.sig_mac, usePrimes=True)
		
		if not it_checks_out:
			err_msg = self.message_factory()
			err_msg.create_error('Signature incorrect')
			self.client.send(err_msg.jabber_msg)
			return None # nothing for user
			#raise Exception('stop here - signature incorrect')
		
		self.auth.set_auth_state("AUTHSTATE_NONE")
		self.auth.set_message_state("MSGSTATE_ENCRYPTED")
		self.auth.dh_keys.reset_session()
		self.auth.authing = False
		self.auth.dh_keys.authing = False
		
		logging.debug( "SUCESSFULLY AUTHENTICATED!" )
		
		return None
		
	def respond_to_v1_key_exchange(self, msg):
		logging.debug("Responding to V1 Key Exchange")
		msg = self.message_factory().create_error('Version 1 Not Supported')
		self.client.send(msg)
		return None
		
	def respond_to_data(self, msg):
		logging.debug("Responding to Data")
		if self.auth.message_state_is("MSGSTATE_ENCRYPTED"):
			# Verify the info in the message
			
			dec_msg = self.auth.dh_keys.receive_data_message(msg)
			logging.debug( dec_msg )
			if len(dec_msg) > 0:
				logging.debug( "DECRYPTED: "+_OT.bytes_to_string(dec_msg) )
				if self.echolalic:
					msg = self.message_factory().make_jabber_message(_OT.bytes_to_string(dec_msg))
					self.process_outgoing(msg)
			# If verification succeeds:
			
			# Decrypt the message and display the human-readable part (if non-empty) to the user.
			
			# Update the D-H encryption keys, if necessary.
			
			# If you have not sent a message to this correspondent in some (configurable) time, 
			# send a "heartbeat" message, consisting of a Data Message encoding an empty plaintext. 
			# The heartbeat message should have the IGNORE_UNREADABLE flag set.
			
			# If the received message contains a TLV type 1, forget all encryption keys 
			# for this correspondent, and transition msgstate to MSGSTATE_FINISHED.
			
		else:
			# Inform the user that an unreadable encrypted message was received, and reply with an Error Message.
			# TODO check for heartbeat messages
			
			logging.debug( "Not ready!" )
			pass
			
		return None
		
	def respond_to_error(self, msg):
		logging.debug("Responding to Error")
		logging.error(msg.error_text)
		if OtrOptions["ERROR_START_AKE"]:
			msg = self.message_factory().create_query()
			self.client.send(msg)
		return None
	
	def respond_to_other(self, msg):
		logging.debug("Responding to Other")
		logging.error("Undecipherable message: "+str(msg.jabber_msg))
		return None
	
	def finish(self):
		if self.auth.message_state_is("MSGSTATE_PLAINTEXT"):
			# Do nothing.
			pass
		elif self.auth.message_state_is("MSGSTATE_ENCRYPTED"):
			# Send a Data Message, encoding a message with an empty human-readable part, 
			# and TLV type 1. Transition msgstate to MSGSTATE_PLAINTEXT.
			# TODO send the data message
			self.auth.set_message_state("MSGSTATE_PLAINTEXT")
		elif self.auth.message_state_is("MSGSTATE_FINISHED"):
			# Transition msgstate to MSGSTATE_PLAINTEXT.
			self.auth.set_message_state("MSGSTATE_PLAINTEXT")
	
	def process_outgoing(self, msg):
		logging.debug("Outgoing")
		if self.auth.message_state_is("MSGSTATE_ENCRYPTED"):
			self.auth.dh_keys.prepare_session_to_send()
			vars = self.auth.dh_keys.encrypt_data_message(_OT.string_to_bytes(msg.getBody()))
			logging.debug( vars )
			#r = raw_input()
			enc_msg = self.message_factory().create_data(*vars)
			self.client.send(enc_msg.jabber_msg)
		else:
			self.client.send(msg)
		return msg
	
	responders = {
	"plaintext"			: respond_to_plaintext, 
	"query"				: respond_to_query,
	"whitespaced"		: respond_to_whitespaced,
	"dh_commit"			: respond_to_dh_commit, 
	"dh_key"			: respond_to_dh_key,
	"reveal_sig"		: respond_to_reveal_sig, 
	"signature"			: respond_to_signature,
	"v1_key_exchange"	: respond_to_v1_key_exchange, 
	"data"				: respond_to_data, 
	"error"				: respond_to_error,
	"other"				: respond_to_other
	}
	
	def process_incoming(self, msg):
		logging.debug("Incoming")
		otr_msg = OtrMessage(msg)
		if not otr_msg.parsed_ok:
			logging.error('Received strange message: %s' % str(msg))
		else:
			return self.responders[otr_msg.type](self, otr_msg)