make python-crypto an optional dependency
This commit is contained in:
		
							parent
							
								
									03dbf641de
								
							
						
					
					
						commit
						62edcc71de
					
				
					 6 changed files with 59 additions and 35 deletions
				
			
		|  | @ -29,6 +29,7 @@ Gajim is a GTK+ app that loves GNOME. You can do 'make' so you don't require gno | ||||||
| <h2>Optional Runtime Requirements</h2> | <h2>Optional Runtime Requirements</h2> | ||||||
| <ul> | <ul> | ||||||
| <li><a href="http://pyopenssl.sourceforge.net/">PyOpenSSL</a> (python-pyopenssl package in Debian) for <em>secure</em> SSL/TLS. Python's default SSL is insecure, so this package is highly recommended!</li> | <li><a href="http://pyopenssl.sourceforge.net/">PyOpenSSL</a> (python-pyopenssl package in Debian) for <em>secure</em> SSL/TLS. Python's default SSL is insecure, so this package is highly recommended!</li> | ||||||
|  | <li>python-crypto to enable End to end encryption</li> | ||||||
| <li>For zeroconf (bonjour), the "enable link-local messaging" checkbox, you need dbus-glib, python-avahi</li> | <li>For zeroconf (bonjour), the "enable link-local messaging" checkbox, you need dbus-glib, python-avahi</li> | ||||||
| <li>dnsutils (or whatever package provides the nslookup binary) for SRV support; if you don't know what that is, you don't need it</li> | <li>dnsutils (or whatever package provides the nslookup binary) for SRV support; if you don't know what that is, you don't need it</li> | ||||||
| <li>gtkspell and aspell-LANG where lang is your locale eg. en, fr etc</li> | <li>gtkspell and aspell-LANG where lang is your locale eg. en, fr etc</li> | ||||||
|  |  | ||||||
|  | @ -1592,6 +1592,9 @@ class ChatControl(ChatControlBase): | ||||||
| 		toggle_gpg_menuitem.set_property('sensitive', is_sensitive) | 		toggle_gpg_menuitem.set_property('sensitive', is_sensitive) | ||||||
| 
 | 
 | ||||||
| 		# TODO: check that the remote client supports e2e | 		# TODO: check that the remote client supports e2e | ||||||
|  | 		if not gajim.HAVE_PYCRYPTO: | ||||||
|  | 			toggle_e2e_menuitem.set_sensitive(False) | ||||||
|  | 		else: | ||||||
| 			isactive = int(self.session != None and self.session.enable_encryption) | 			isactive = int(self.session != None and self.session.enable_encryption) | ||||||
| 			toggle_e2e_menuitem.set_active(isactive) | 			toggle_e2e_menuitem.set_active(isactive) | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -1490,6 +1490,7 @@ class ConnectionHandlers(ConnectionVcard, ConnectionBytestream, ConnectionDisco, | ||||||
| 			return | 			return | ||||||
| 		if msg.getTag('feature') and msg.getTag('feature').namespace == \ | 		if msg.getTag('feature') and msg.getTag('feature').namespace == \ | ||||||
| 		common.xmpp.NS_FEATURE: | 		common.xmpp.NS_FEATURE: | ||||||
|  | 			if gajim.HAVE_PYCRYPTO: | ||||||
| 				self._FeatureNegCB(con, msg, session) | 				self._FeatureNegCB(con, msg, session) | ||||||
| 			return | 			return | ||||||
| 		if msg.getTag('init') and msg.getTag('init').namespace == \ | 		if msg.getTag('init') and msg.getTag('init').namespace == \ | ||||||
|  |  | ||||||
|  | @ -136,6 +136,16 @@ priority_dict = {} | ||||||
| for status in ('online', 'chat', 'away', 'xa', 'dnd', 'invisible'): | for status in ('online', 'chat', 'away', 'xa', 'dnd', 'invisible'): | ||||||
| 	priority_dict[status] = config.get('autopriority' + status) | 	priority_dict[status] = config.get('autopriority' + status) | ||||||
| 
 | 
 | ||||||
|  | HAVE_PYCRYPTO = True | ||||||
|  | try: | ||||||
|  | 	from Crypto.PublicKey.RSA import generate | ||||||
|  | except ImportError: | ||||||
|  | 	HAVE_PYCRYPTO = False | ||||||
|  | else: | ||||||
|  | 	# public key for XEP-0116 | ||||||
|  | 	#FIXME os.urandom is not a cryptographic PRNG | ||||||
|  | 	pubkey = Crypto.PublicKey.RSA.generate(384, os.urandom) | ||||||
|  | 
 | ||||||
| def get_nick_from_jid(jid): | def get_nick_from_jid(jid): | ||||||
| 	pos = jid.find('@') | 	pos = jid.find('@') | ||||||
| 	return jid[:pos] | 	return jid[:pos] | ||||||
|  |  | ||||||
|  | @ -14,10 +14,6 @@ import time | ||||||
| from common import dh | from common import dh | ||||||
| import xmpp.c14n | import xmpp.c14n | ||||||
| 
 | 
 | ||||||
| from Crypto.Cipher import AES |  | ||||||
| from Crypto.Hash import HMAC, SHA256 |  | ||||||
| from Crypto.PublicKey import RSA |  | ||||||
| 
 |  | ||||||
| import base64 | import base64 | ||||||
| 
 | 
 | ||||||
| XmlDsig = 'http://www.w3.org/2000/09/xmldsig#' | XmlDsig = 'http://www.w3.org/2000/09/xmldsig#' | ||||||
|  | @ -99,25 +95,31 @@ class StanzaSession(object): | ||||||
| 		# we could send an acknowledgement message here, but we won't. | 		# we could send an acknowledgement message here, but we won't. | ||||||
| 		self.status = None | 		self.status = None | ||||||
| 
 | 
 | ||||||
| # an encrypted stanza negotiation has several states. i've represented them as the following values in the 'status'  | if gajim.HAVE_PYCRYPTO: | ||||||
|  | 	from Crypto.Cipher import AES | ||||||
|  | 	from Crypto.Hash import HMAC, SHA256 | ||||||
|  | 	from Crypto.PublicKey import RSA | ||||||
|  | 
 | ||||||
|  | # an encrypted stanza negotiation has several states. i've represented them | ||||||
|  | # as the following values in the 'status'  | ||||||
| # attribute of the session object: | # attribute of the session object: | ||||||
| 
 | 
 | ||||||
| # 1. None: | # 1. None: | ||||||
| #				default state | #				default state | ||||||
| # 2. 'requested-e2e': | # 2. 'requested-e2e': | ||||||
| #				this client has initiated an esession negotiation and is waiting for | #				this client has initiated an esession negotiation and is waiting | ||||||
| #				a response | #				for a response | ||||||
| # 3. 'responded-e2e': | # 3. 'responded-e2e': | ||||||
| #				this client has responded to an esession negotiation request and is | #				this client has responded to an esession negotiation request and | ||||||
| #				waiting for the initiator to identify itself and complete the | #				is waiting for the initiator to identify itself and complete the | ||||||
| #				negotiation | #				negotiation | ||||||
| # 4. 'identified-alice': | # 4. 'identified-alice': | ||||||
| #				this client identified itself and is waiting for the responder to | #				this client identified itself and is waiting for the responder to | ||||||
| #				identify itself and complete the negotiation | #				identify itself and complete the negotiation | ||||||
| # 5. 'active': | # 5. 'active': | ||||||
| #				an encrypted session has been successfully negotiated. messages of | #				an encrypted session has been successfully negotiated. messages | ||||||
| #				any of the types listed in 'encryptable_stanzas' should be encrypted | #				of any of the types listed in 'encryptable_stanzas' should be | ||||||
| # 			before they're sent. | #				encrypted before they're sent. | ||||||
| 
 | 
 | ||||||
| # the transition between these states is handled in gajim.py's | # the transition between these states is handled in gajim.py's | ||||||
| #	handle_session_negotiation method. | #	handle_session_negotiation method. | ||||||
|  | @ -144,7 +146,8 @@ class EncryptedStanzaSession(StanzaSession): | ||||||
| 	# 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 | ||||||
| 		self.encrypter = self.cipher.new(self._kc_s, self.cipher.MODE_CTR, counter=self.encryptcounter) | 		self.encrypter = self.cipher.new(self._kc_s, self.cipher.MODE_CTR, | ||||||
|  | 			counter=self.encryptcounter) | ||||||
| 
 | 
 | ||||||
| 	def get_kc_s(self): | 	def get_kc_s(self): | ||||||
| 		return self._kc_s | 		return self._kc_s | ||||||
|  | @ -152,7 +155,8 @@ class EncryptedStanzaSession(StanzaSession): | ||||||
| 	# keep the decrypter updated with the other party's latest cipher key | 	# keep the decrypter updated with the other party's latest cipher key | ||||||
| 	def set_kc_o(self, value): | 	def set_kc_o(self, value): | ||||||
| 		self._kc_o = value | 		self._kc_o = value | ||||||
| 		self.decrypter = self.cipher.new(self._kc_o, self.cipher.MODE_CTR, counter=self.decryptcounter) | 		self.decrypter = self.cipher.new(self._kc_o, self.cipher.MODE_CTR, | ||||||
|  | 			counter=self.decryptcounter) | ||||||
| 	 | 	 | ||||||
| 	def get_kc_o(self): | 	def get_kc_o(self): | ||||||
| 		return self._kc_o | 		return self._kc_o | ||||||
|  | @ -167,7 +171,8 @@ class EncryptedStanzaSession(StanzaSession): | ||||||
| 		else: | 		else: | ||||||
| 			return chr(n) | 			return chr(n) | ||||||
| 
 | 
 | ||||||
| 	# convert a large integer to a big-endian bitstring, padded with \x00s to 16 bytes | 	# convert a large integer to a big-endian bitstring, padded with \x00s to | ||||||
|  | 	# 16 bytes | ||||||
| 	def encode_mpi_with_padding(self, n): | 	def encode_mpi_with_padding(self, n): | ||||||
| 		ret = self.encode_mpi(n) | 		ret = self.encode_mpi(n) | ||||||
| 
 | 
 | ||||||
|  | @ -195,13 +200,16 @@ class EncryptedStanzaSession(StanzaSession): | ||||||
| 	def sign(self, string): | 	def sign(self, string): | ||||||
| 		if self.negotiated['sign_algs'] == (XmlDsig + 'rsa-sha256'): | 		if self.negotiated['sign_algs'] == (XmlDsig + 'rsa-sha256'): | ||||||
| 			hash = self.sha256(string) | 			hash = self.sha256(string) | ||||||
| 			return self.encode_mpi(gajim.interface.pubkey.sign(hash, '')[0]) | 			return self.encode_mpi(gajim.pubkey.sign(hash, '')[0]) | ||||||
| 
 | 
 | ||||||
| 	def encrypt_stanza(self, stanza): | 	def encrypt_stanza(self, stanza): | ||||||
| 		encryptable = filter(lambda x: x.getName() not in ('error', 'amp', 'thread'), stanza.getChildren()) | 		encryptable = filter(lambda x: x.getName() not in ('error', 'amp', | ||||||
|  | 			'thread'), stanza.getChildren()) | ||||||
| 
 | 
 | ||||||
| 		# XXX can also encrypt contents of <error/> elements in stanzas @type = 'error' | 		# XXX can also encrypt contents of <error/> elements in stanzas @type = | ||||||
| 		# (except for <defined-condition xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/> child elements) | 		# 'error' | ||||||
|  | 		# (except for <defined-condition | ||||||
|  | 		# xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/> child elements) | ||||||
| 
 | 
 | ||||||
| 		old_en_counter = self.c_s | 		old_en_counter = self.c_s | ||||||
| 
 | 
 | ||||||
|  | @ -220,7 +228,8 @@ class EncryptedStanzaSession(StanzaSession): | ||||||
| 		# XXX check for rekey request, handle <key/> elements | 		# XXX check for rekey request, handle <key/> elements | ||||||
| 
 | 
 | ||||||
| 		m_content = ''.join(map(str, c.getChildren())) | 		m_content = ''.join(map(str, c.getChildren())) | ||||||
| 		c.NT.mac = base64.b64encode(self.hmac(self.km_s, m_content + self.encode_mpi(old_en_counter))) | 		c.NT.mac = base64.b64encode(self.hmac(self.km_s, m_content + \ | ||||||
|  | 			self.encode_mpi(old_en_counter))) | ||||||
| 
 | 
 | ||||||
| 		return stanza | 		return stanza | ||||||
| 
 | 
 | ||||||
|  | @ -278,15 +287,18 @@ class EncryptedStanzaSession(StanzaSession): | ||||||
| 		return self.random_bytes(8) | 		return self.random_bytes(8) | ||||||
| 
 | 
 | ||||||
| 	def decrypt_stanza(self, stanza): | 	def decrypt_stanza(self, stanza): | ||||||
| 		c = stanza.getTag(name='c', namespace='http://www.xmpp.org/extensions/xep-0200.html#ns') | 		c = stanza.getTag(name='c', | ||||||
|  | 			namespace='http://www.xmpp.org/extensions/xep-0200.html#ns') | ||||||
| 
 | 
 | ||||||
| 		stanza.delChild(c) | 		stanza.delChild(c) | ||||||
| 
 | 
 | ||||||
| 		# contents of <c>, minus <mac>, minus whitespace | 		# contents of <c>, minus <mac>, minus whitespace | ||||||
| 		macable = ''.join(map(str, filter(lambda x: x.getName() != 'mac', c.getChildren()))) | 		macable = ''.join(map(str, filter(lambda x: x.getName() != 'mac', | ||||||
|  | 			c.getChildren()))) | ||||||
| 
 | 
 | ||||||
| 		received_mac = base64.b64decode(c.getTagData('mac')) | 		received_mac = base64.b64decode(c.getTagData('mac')) | ||||||
| 		calculated_mac = self.hmac(self.km_o, macable + self.encode_mpi_with_padding(self.c_o)) | 		calculated_mac = self.hmac(self.km_o, macable + \ | ||||||
|  | 			self.encode_mpi_with_padding(self.c_o)) | ||||||
| 
 | 
 | ||||||
| 		if not calculated_mac == received_mac: | 		if not calculated_mac == received_mac: | ||||||
| 			raise exceptions.DecryptionError, 'bad signature' | 			raise exceptions.DecryptionError, 'bad signature' | ||||||
|  | @ -351,7 +363,8 @@ class EncryptedStanzaSession(StanzaSession): | ||||||
| 				if self.negotiated['sign_algs'] == (XmlDsig + 'rsa-sha256'): | 				if self.negotiated['sign_algs'] == (XmlDsig + 'rsa-sha256'): | ||||||
| 					keyvalue = parsed.getTag(name='RSAKeyValue', namespace=XmlDsig) | 					keyvalue = parsed.getTag(name='RSAKeyValue', namespace=XmlDsig) | ||||||
| 
 | 
 | ||||||
| 					n, e = map(lambda x: self.decode_mpi(base64.b64decode(keyvalue.getTagData(x))), ('Modulus', 'Exponent')) | 					n, e = map(lambda x: self.decode_mpi(base64.b64decode( | ||||||
|  | 						keyvalue.getTagData(x))), ('Modulus', 'Exponent')) | ||||||
| 					eir_pubkey = RSA.construct((n,long(e))) | 					eir_pubkey = RSA.construct((n,long(e))) | ||||||
| 
 | 
 | ||||||
| 					pubkey_o = xmpp.c14n.c14n(keyvalue) | 					pubkey_o = xmpp.c14n.c14n(keyvalue) | ||||||
|  | @ -359,7 +372,8 @@ class EncryptedStanzaSession(StanzaSession): | ||||||
| 					# XXX DSA, etc. | 					# XXX DSA, etc. | ||||||
| 					raise 'unimplemented' | 					raise 'unimplemented' | ||||||
| 
 | 
 | ||||||
| 			enc_sig = parsed.getTag(name='SignatureValue', namespace=XmlDsig).getData() | 			enc_sig = parsed.getTag(name='SignatureValue', | ||||||
|  | 				namespace=XmlDsig).getData() | ||||||
| 			signature = (self.decode_mpi(base64.b64decode(enc_sig)),) | 			signature = (self.decode_mpi(base64.b64decode(enc_sig)),) | ||||||
| 		else: | 		else: | ||||||
| 			mac_o = self.decrypt(id_o) | 			mac_o = self.decrypt(id_o) | ||||||
|  | @ -390,7 +404,7 @@ class EncryptedStanzaSession(StanzaSession): | ||||||
| 	def make_identity(self, form, dh_i): | 	def make_identity(self, form, dh_i): | ||||||
| 		if self.negotiated['send_pubkey']: | 		if self.negotiated['send_pubkey']: | ||||||
| 			if self.negotiated['sign_algs'] == (XmlDsig + 'rsa-sha256'): | 			if self.negotiated['sign_algs'] == (XmlDsig + 'rsa-sha256'): | ||||||
| 				fields = (gajim.interface.pubkey.n, gajim.interface.pubkey.e) | 				fields = (gajim.pubkey.n, gajim.pubkey.e) | ||||||
| 
 | 
 | ||||||
| 				cb_fields = map(lambda f: base64.b64encode(self.encode_mpi(f)), fields) | 				cb_fields = map(lambda f: base64.b64encode(self.encode_mpi(f)), fields) | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -118,7 +118,6 @@ from chat_control import ChatControlBase | ||||||
| from atom_window import AtomWindow | from atom_window import AtomWindow | ||||||
| 
 | 
 | ||||||
| import negotiation | import negotiation | ||||||
| import Crypto.PublicKey.RSA |  | ||||||
| 
 | 
 | ||||||
| from common import exceptions | from common import exceptions | ||||||
| from common.zeroconf import connection_zeroconf | from common.zeroconf import connection_zeroconf | ||||||
|  | @ -2765,10 +2764,6 @@ class Interface: | ||||||
| 			gobject.timeout_add(2000, self.process_connections) | 			gobject.timeout_add(2000, self.process_connections) | ||||||
| 		gobject.timeout_add(10000, self.read_sleepy) | 		gobject.timeout_add(10000, self.read_sleepy) | ||||||
| 
 | 
 | ||||||
| 		# public key for XEP-0116 |  | ||||||
| 		# XXX os.urandom is not a cryptographic PRNG |  | ||||||
| 		self.pubkey = Crypto.PublicKey.RSA.generate(384, os.urandom) |  | ||||||
| 
 |  | ||||||
| if __name__ == '__main__': | if __name__ == '__main__': | ||||||
| 	def sigint_cb(num, stack): | 	def sigint_cb(num, stack): | ||||||
| 		sys.exit(5) | 		sys.exit(5) | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		
		Reference in a new issue