summaryrefslogtreecommitdiff
path: root/plugins/gpg/__init__.py
diff options
context:
space:
mode:
Diffstat (limited to 'plugins/gpg/__init__.py')
-rw-r--r--plugins/gpg/__init__.py338
1 files changed, 0 insertions, 338 deletions
diff --git a/plugins/gpg/__init__.py b/plugins/gpg/__init__.py
deleted file mode 100644
index 0282c93a..00000000
--- a/plugins/gpg/__init__.py
+++ /dev/null
@@ -1,338 +0,0 @@
-"""
-This plugin implements the `XEP-0027`_ “Current Jabber OpenPGP Usage”.
-
-This is a plugin used to encrypt one-to-one conversation using the PGP
-encryption method. You can use it if you want really good privacy. Without this
-encryption, your messages are encrypted **at least** from your client (poezio) to
-your server. The message is decrypted by your server and you cannot control the
-encryption method of your messages from your server to your contact’s server
-(unless you are your own server’s administrator), nor from your contact’s
-server to your contact’s client.
-
-This plugin does end-to-end encryption. This means that **only** your contact can
-decrypt your messages, and it is fully encrypted during **all** its travel
-through the internet.
-
-Note that if you are having an encrypted conversation with a contact, you can
-**not** send XHTML-IM messages to him. They will be removed and be replaced by
-plain text messages.
-
-Installation and configuration
-------------------------------
-
-You should autoload this plugin, as it will send your signed presence directly
-on login, making it easier for your contact’s clients to know that you are
-supporting GPG encryption. To do that, use the :term:`plugins_autoload` configuration
-option.
-
-You need to create a plugin configuration file. Create a file named :file:`gpg.cfg`
-into your plugins configuration directory (:file:`~/.config/poezio/plugins` by
-default), and fill it like this:
-
-.. code-block:: ini
-
- [gpg]
- keyid = 091F9C78
- passphrase = your OPTIONAL passphrase
-
- [keys]
- example@jabber.org = E3CFCDE2
- juliet@xmpp.org = EF27ABCD
-
-The ``gpg`` section is about your key. You need to specify the keyid, for the
-key you want to use. You can as well provide a passphrase. If you don’t, you
-should use a gpg agent or something like that that will ask your passphrase
-whenever you need it.
-
-The ``keys`` section contains your contact’s id keys. For each contact you want
-to have encrypted conversations with, add her/his JID associated with the keyid
-of his/her key. You can autogenerate a keys section based on the ones already
-in your trust chain by running the script ``poezio_gpg_export`` provided with
-poezio (in the :file:`scripts/` directory). Please double-check the section
-created this way.
-
-And that’s it, now you need to talk directly to the **full** jid of your
-contacts. Poezio doesn’t let you encrypt messages whom recipients is a bare
-JID.
-
-Additionnal information on GnuPG
---------------------------------
-
-Create a key
-~~~~~~~~~~~~
-
-To create a personal key, use
-
-.. code-block:: bash
-
- gpg --gen-key
-
-and follow the instructions.
-
-Keyid
-~~~~~
-The keyid (required in the gpg.cfg configuration file) is a 8 character-long
-key. You can get the ones you created or imported by using the command
-
-.. code-block:: bash
-
- gpg --list-keys
-
-You will get something like
-
-.. code-block:: none
-
- pub 4096R/01234567 2011-11-11
- uid Your Name Here (comment) <email@example.org>
- sub 4096R/AAFFBBCC 2011-11-11
-
- pub 2048R/12345678 2011-11-12 [expire: 2011-11-22]
- uid A contact’s name (comment) <fake@fake.fr>
- sub 2048R/FFBBAACC 2011-11-12 [expire: 2011-11-22]
-
-In this example, the keyids are ``01234567`` and ``12345678``.
-
-Share your key
-~~~~~~~~~~~~~~
-Use:
-
-.. code-block:: bash
-
- gpg --send-keys --keyserver pgp.mit.edu <keyid>
-
-to upload you public key on a public server.
-
-.. _XEP-0027: http://xmpp.org/extensions/xep-0027.html
-
-"""
-from gpg import gnupg
-from slixmpp.xmlstream.stanzabase import JID
-
-from xml.etree import cElementTree as ET
-import xml.sax.saxutils
-
-import logging
-log = logging.getLogger(__name__)
-
-from poezio.plugin import BasePlugin
-
-from poezio.tabs import ConversationTab
-from poezio.theming import get_theme
-
-NS_SIGNED = "jabber:x:signed"
-NS_ENCRYPTED = "jabber:x:encrypted"
-
-
-SIGNED_ATTACHED_MESSAGE = """-----BEGIN PGP SIGNED MESSAGE-----
-Hash: %(hash)s
-
-%(clear)s
------BEGIN PGP SIGNATURE-----
-
-%(data)s
------END PGP SIGNATURE-----
-"""
-
-
-ENCRYPTED_MESSAGE = """-----BEGIN PGP MESSAGE-----
-
-%(data)s
------END PGP MESSAGE-----"""
-
-
-class Plugin(BasePlugin):
- def init(self):
- self.contacts = {}
- # a dict of {full-JID: 'signed'/'valid'/'invalid'/'disabled'}
- # Whenever we receive a signed presence from a JID, we add it to this
- # dict, this way we know if we can encrypt the messages we will send to
- # this JID.
- # If that resource sends a non-signed presence, then we remove it
- # from that dict and stop encrypting our messages.
- # 'disabled' means that the user do NOT want to encrypt its messages
- # even if the key is valid.
- self.gpg = gnupg.GPG()
- self.keyid = self.config.get('keyid', '') or None
- self.passphrase = self.config.get('passphrase', '') or None
- if not self.keyid:
- self.api.information('No GPG keyid provided in the configuration', 'Warning')
-
- self.api.add_event_handler('send_normal_presence', self.sign_presence)
- self.api.add_slix_event_handler('presence', self.on_normal_presence)
- self.api.add_event_handler('conversation_say_after', self.on_conversation_say)
- self.api.add_event_handler('conversation_msg', self.on_conversation_msg)
-
- self.api.add_tab_command(ConversationTab, 'gpg', self.command_gpg,
- usage='<force|disable|setkey> [jid] [keyid]',
- help='Force or disable gpg encryption with the fulljid of the current conversation. The setkey argument lets you associate a keyid with the given bare JID.',
- short='Manage the GPG status',
- completion=self.gpg_completion)
- ConversationTab.add_information_element('gpg', self.display_encryption_status)
-
- def cleanup(self):
- self.send_unsigned_presence()
- ConversationTab.remove_information_element('gpg')
- self.del_tab_command(ConversationTab, 'gpg')
-
- def sign_presence(self, presence):
- """
- Sign every normal presence we send
- """
- signed_element = ET.Element('{%s}x' % (NS_SIGNED,))
- t = self.gpg.sign(presence['status'], keyid=self.keyid, passphrase=self.passphrase, detach=True)
- if not t:
- self.core.information('Could not sign presence. Disabling GPG module', 'Info')
- self.core.plugin_manager.unload('gpg')
- return
- text = xml.sax.saxutils.escape(str(t))
- signed_element.text = self.remove_gpg_headers(text)
- presence.append(signed_element)
-
- def send_unsigned_presence(self):
- """
- Send our current presence, to everyone, but unsigned, to indicate
- that we cannot/do not want to encrypt/decrypt messages.
- """
- current_presence = self.core.get_status()
- self.core.command.status('%s %s' % (current_presence.show or 'available', current_presence.message or '',))
-
- def on_normal_presence(self, presence):
- """
- Check if it’s signed, if it is and we can verify the signature,
- add 'valid' or 'invalid' into the dict. If it cannot be verified, just add
- 'signed'. Otherwise, do nothing.
- """
- signed = presence.xml.find('{%s}x' % (NS_SIGNED,))
- bare = presence['from'].bare
- full = presence['from'].full
- if signed is None:
- if bare in self.contacts.keys():
- del self.contacts[bare]
- return
- if self.config.has_section('keys') and bare in self.config.options('keys'):
- self.contacts[full] = 'invalid'
- for hash_ in ('SHA1', 'SHA256', 'SHA512'):
- to_verify = SIGNED_ATTACHED_MESSAGE % {'clear': presence['status'],
- 'data': signed.text,
- 'hash': hash_}
- verify = self.gpg.verify(to_verify)
- if verify:
- self.contacts[full] = 'valid'
- break
- else:
- self.contacts[full] = 'signed'
-
- def on_conversation_say(self, message, tab):
- """
- Check if the contact has a signed AND verified signature.
- If yes, encrypt the message with her key.
- """
- to = message['to']
- if not message['body']:
- # there’s nothing to encrypt if this is a chatstate, for example
- return
- signed = to.full in self.contacts.keys()
- if signed:
- verified = self.contacts[to.full] in ('valid', 'forced')
- else:
- verified = False
- if verified:
- # remove the xhtm_im body if present, because that
- # cannot be encrypted.
- body = message['body']
- del message['html']
- encrypted_element = ET.Element('{%s}x' % (NS_ENCRYPTED,))
- text = self.gpg.encrypt(message['body'], self.config.get(to.bare, '', section='keys'), always_trust=True)
- if not text:
- self.core.information('Could not encrypt message to %s' % (to.full),)
- # If we could not encrypt the message, don't send anything
- message['body'] = ''
- return
- encrypted_element.text = self.remove_gpg_headers(xml.sax.saxutils.escape(str(text)))
- message.append(encrypted_element)
- message['body'] = 'This message has been encrypted using the GPG key with id: %s' % self.keyid
- message['eme']['namespace'] = 'jabber:x:encrypted'
- message.send()
- del message['body']
- tab.add_message(body, nickname=self.core.own_nick,
- nick_color=get_theme().COLOR_OWN_NICK,
- identifier=message['id'],
- jid=self.core.xmpp.boundjid,
- typ=0)
-
- def on_conversation_msg(self, message, tab):
- """
- Check if the message is encrypted, and decrypt it if we can.
- """
- encrypted = message.xml.find('{%s}x' % (NS_ENCRYPTED,))
- fro = message['from']
- if encrypted is not None:
- if self.config.has_section('keys') and fro.bare in self.config.options('keys'):
- keyid = self.config.get(fro.bare, '', 'keys')
- decrypted = self.gpg.decrypt(ENCRYPTED_MESSAGE % {'data': str(encrypted.text)}, passphrase=self.passphrase)
- if not decrypted:
- self.core.information('Could not decrypt message from %s' % (fro.full),)
- return
- message['body'] = str(decrypted)
-
- def display_encryption_status(self, jid):
- """
- Returns the status of encryption for the associated jid. This is to be used
- in the ConversationTab’s InfoWin.
- """
- if jid.full not in self.contacts.keys():
- return ''
- status = self.contacts[jid.full]
- if status in ('valid', 'invalid', 'signed'):
- return ' GPG Key: %s (%s)' % (status, 'encrypted' if status == 'valid' else 'NOT encrypted',)
- else:
- return ' GPG: Encryption %s' % (status,)
-
- def command_gpg(self, args):
- """
- A command to force or disable the encryption, or to assign a keyid to a JID
- """
- args = args.split()
- if not args:
- return self.core.command.help("gpg")
- if len(args) >= 2:
- jid = JID(args[1])
- else:
- if isinstance(self.core.current_tab(), ConversationTab):
- jid = JID(self.core.current_tab().name)
- else:
- return
- command = args[0]
- if command == 'force' or command == 'enable':
- # we can force encryption only with contact having an associated
- # key, otherwise we cannot encrypt at all
- if self.config.has_section('keys') and jid.bare in self.config.options('keys'):
- self.contacts[JID(jid).full] = 'forced'
- else:
- self.core.information('Cannot force encryption: no key associated with %s' % (jid.bare), 'Info')
- elif command == 'disable':
- self.contacts[JID(jid).full] = 'disabled'
- elif command == 'setkey':
- if len(args) != 3:
- return self.core.command.help("gpg")
- if not self.config.has_section('keys'):
- self.config.add_section('keys')
- self.config.set(jid.bare, args[2], 'keys')
- self.config.write()
- self.core.refresh_window()
-
- def gpg_completion(self, the_input):
- if the_input.get_argument_position() == 1:
- return the_input.new_completion(['force', 'disable', 'setkey'], 1, quotify=False)
-
- def remove_gpg_headers(self, text):
- lines = text.splitlines()
- while lines[0].strip() != '':
- lines.pop(0)
- while lines[0].strip() == '':
- lines.pop(0)
- res = []
- for line in lines:
- if not line.startswith('---'):
- res.append(line)
- return '\n'.join(res)