Commit 2b9780a9 authored by Philipp Hörist's avatar Philipp Hörist
Browse files

[openpgp] Refactor Plugin

- Adapt to nbxmpp now supporting openpgp
parent 0189214c
# Copyright (C) 2018 Philipp Hörist <philipp AT hoerist.com>
# Copyright (C) 2019 Philipp Hörist <philipp AT hoerist.com>
#
# This file is part of Gajim.
# This file is part of the OpenPGP Gajim Plugin.
#
# Gajim is free software; you can redistribute it and/or modify
# OpenPGP Gajim Plugin is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published
# by the Free Software Foundation; version 3 only.
#
# Gajim is distributed in the hope that it will be useful,
# OpenPGP Gajim Plugin is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Gajim. If not, see <http://www.gnu.org/licenses/>.
# XEP-0373: OpenPGP for XMPP
# along with OpenPGP Gajim Plugin. If not, see <http://www.gnu.org/licenses/>.
import io
......
# Copyright (C) 2018 Philipp Hörist <philipp AT hoerist.com>
# Copyright (C) 2019 Philipp Hörist <philipp AT hoerist.com>
#
# This file is part of Gajim.
# This file is part of the OpenPGP Gajim Plugin.
#
# Gajim is free software; you can redistribute it and/or modify
# OpenPGP Gajim Plugin is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published
# by the Free Software Foundation; version 3 only.
#
# Gajim is distributed in the hope that it will be useful,
# OpenPGP Gajim Plugin is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Gajim. If not, see <http://www.gnu.org/licenses/>.
# XEP-0373: OpenPGP for XMPP
# along with OpenPGP Gajim Plugin. If not, see <http://www.gnu.org/licenses/>.
import os
import logging
......@@ -27,7 +25,6 @@ from gajim.common import app
from openpgp.modules.util import DecryptionFailed
log = logging.getLogger('gajim.plugin_system.openpgp.pygnupg')
# gnupg.logger = log
KeyringItem = namedtuple('KeyringItem', 'jid keyid fingerprint')
......@@ -38,7 +35,7 @@ class PGPContext(gnupg.GPG):
self, gpgbinary=app.get_gpg_binary(), gnupghome=str(gnupghome))
self._passphrase = 'gajimopenpgppassphrase'
self._jid = jid
self._jid = jid.getBare()
self._own_fingerprint = None
def _get_key_params(self, jid, passphrase):
......@@ -122,7 +119,7 @@ class PGPContext(gnupg.GPG):
log.error(result.results[0])
return
if not self.validate_key(data, jid):
if not self.validate_key(data, str(jid)):
return None
key = self.get_key(result.results[0]['fingerprint'])
return self._make_keyring_item(key[0])
......
# Copyright (C) 2018 Philipp Hörist <philipp AT hoerist.com>
# Copyright (C) 2019 Philipp Hörist <philipp AT hoerist.com>
#
# This file is part of Gajim.
# This file is part of the OpenPGP Gajim Plugin.
#
# Gajim is free software; you can redistribute it and/or modify
# OpenPGP Gajim Plugin is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published
# by the Free Software Foundation; version 3 only.
#
# Gajim is distributed in the hope that it will be useful,
# OpenPGP Gajim Plugin is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Gajim. If not, see <http://www.gnu.org/licenses/>.
# XEP-0373: OpenPGP for XMPP
# along with OpenPGP Gajim Plugin. If not, see <http://www.gnu.org/licenses/>.
import sqlite3
import logging
from collections import namedtuple
from nbxmpp import JID
log = logging.getLogger('gajim.plugin_system.openpgp.sql')
TABLE_LAYOUT = '''
CREATE TABLE contacts (
jid TEXT,
jid JID,
fingerprint TEXT,
active BOOLEAN,
trust INTEGER,
......@@ -34,10 +34,25 @@ TABLE_LAYOUT = '''
CREATE UNIQUE INDEX jid_fingerprint ON contacts (jid, fingerprint);'''
def _jid_adapter(jid):
return str(jid)
def _jid_converter(jid):
return JID(jid.decode())
sqlite3.register_adapter(JID, _jid_adapter)
sqlite3.register_converter('JID', _jid_converter)
class Storage:
def __init__(self, folder_path):
self._con = sqlite3.connect(str(folder_path / 'contacts.db'),
detect_types=sqlite3.PARSE_DECLTYPES)
self._con.row_factory = self._namedtuple_factory
self._create_database()
self._migrate_database()
......@@ -72,10 +87,7 @@ class Storage:
pass
def load_contacts(self):
sql = 'SELECT * from contacts'
rows = self._con.execute(sql).fetchall()
if rows is not None:
return rows
return self._con.execute('SELECT * from contacts').fetchall()
def save_contact(self, db_values):
sql = '''REPLACE INTO
......
# Copyright (C) 2018 Philipp Hörist <philipp AT hoerist.com>
# Copyright (C) 2019 Philipp Hörist <philipp AT hoerist.com>
#
# This file is part of Gajim.
# This file is part of the OpenPGP Gajim Plugin.
#
# Gajim is free software; you can redistribute it and/or modify
# OpenPGP Gajim Plugin is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published
# by the Free Software Foundation; version 3 only.
#
# Gajim is distributed in the hope that it will be useful,
# OpenPGP Gajim Plugin is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Gajim. If not, see <http://www.gnu.org/licenses/>.
# XEP-0373: OpenPGP for XMPP
# along with OpenPGP Gajim Plugin. If not, see <http://www.gnu.org/licenses/>.
import logging
import time
......@@ -24,7 +22,7 @@ from gi.repository import Gtk
from gajim.common import app
from gajim.common.const import DialogButton, ButtonAction
from gajim.gtk import NewConfirmationDialog
from gajim.gtk.dialogs import NewConfirmationDialog
from gajim.plugins.plugins_i18n import _
from openpgp.modules.util import Trust
......@@ -49,8 +47,8 @@ TRUST_DATA = {
class KeyDialog(Gtk.Dialog):
def __init__(self, account, jid, transient):
flags = Gtk.DialogFlags.DESTROY_WITH_PARENT
super().__init__(_('Public Keys for %s') % jid, None, flags)
super().__init__(title=_('Public Keys for %s') % jid,
destroy_with_parent=True)
self.set_transient_for(transient)
self.set_resizable(True)
......
# Copyright (C) 2018 Philipp Hörist <philipp AT hoerist.com>
# Copyright (C) 2019 Philipp Hörist <philipp AT hoerist.com>
#
# This file is part of Gajim.
# This file is part of the OpenPGP Gajim Plugin.
#
# Gajim is free software; you can redistribute it and/or modify
# OpenPGP Gajim Plugin is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published
# by the Free Software Foundation; version 3 only.
#
# Gajim is distributed in the hope that it will be useful,
# OpenPGP Gajim Plugin is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Gajim. If not, see <http://www.gnu.org/licenses/>.
# XEP-0373: OpenPGP for XMPP
# along with OpenPGP Gajim Plugin. If not, see <http://www.gnu.org/licenses/>.
import logging
import threading
......@@ -177,8 +175,8 @@ class NewKeyPage(RequestPage):
error = e
else:
self._con.get_module('OpenPGP').get_own_key_details()
self._con.get_module('OpenPGP').publish_key()
self._con.get_module('OpenPGP').query_key_list()
self._con.get_module('OpenPGP').set_public_key()
self._con.get_module('OpenPGP').request_keylist()
GLib.idle_add(self.finished, error)
def finished(self, error):
......
# Copyright (C) 2019 Philipp Hörist <philipp AT hoerist.com>
#
# This file is part of the OpenPGP Gajim Plugin.
#
# OpenPGP Gajim Plugin is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published
# by the Free Software Foundation; version 3 only.
#
# OpenPGP Gajim Plugin is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with OpenPGP Gajim Plugin. If not, see <http://www.gnu.org/licenses/>.
import logging
from openpgp.modules.util import Trust
log = logging.getLogger('gajim.plugin_system.openpgp.store')
class KeyData:
'''
Holds all data related to a certain key
'''
def __init__(self, contact_data):
self._contact_data = contact_data
self.fingerprint = None
self.active = False
self._trust = Trust.UNKNOWN
self.timestamp = None
self.comment = None
self.has_pubkey = False
@property
def trust(self):
return self._trust
@trust.setter
def trust(self, value):
if value not in (Trust.NOT_TRUSTED,
Trust.UNKNOWN,
Trust.BLIND,
Trust.VERIFIED):
raise ValueError('Trust value not allowed: %s' % value)
self._trust = value
self._contact_data.set_trust(self.fingerprint, self._trust)
@classmethod
def from_key(cls, contact_data, key, trust):
keydata = cls(contact_data)
keydata.fingerprint = key.fingerprint
keydata.timestamp = key.date
keydata.active = True
keydata._trust = trust
return keydata
@classmethod
def from_row(cls, contact_data, row):
keydata = cls(contact_data)
keydata.fingerprint = row.fingerprint
keydata.timestamp = row.timestamp
keydata.comment = row.comment
keydata._trust = row.trust
keydata.active = row.active
return keydata
def delete(self):
self._contact_data.delete_key(self.fingerprint)
class ContactData:
'''
Holds all data related to a contact
'''
def __init__(self, jid, storage, pgp):
self.jid = jid
self._key_store = {}
self._storage = storage
self._pgp = pgp
@property
def userid(self):
if self.jid is None:
raise ValueError('JID not set')
return 'xmpp:%s' % self.jid
@property
def default_trust(self):
for key in self._key_store.values():
if key.trust in (Trust.NOT_TRUSTED, Trust.BLIND):
return Trust.UNKNOWN
return Trust.BLIND
def db_values(self):
for key in self._key_store.values():
yield (self.jid,
key.fingerprint,
key.active,
key.trust,
key.timestamp,
key.comment)
def add_from_key(self, key):
try:
keydata = self._key_store[key.fingerprint]
except KeyError:
keydata = KeyData.from_key(self, key, self.default_trust)
self._key_store[key.fingerprint] = keydata
log.info('Add from key: %s %s', self.jid, keydata.fingerprint)
return keydata
def add_from_db(self, row):
try:
keydata = self._key_store[row.fingerprint]
except KeyError:
keydata = KeyData.from_row(self, row)
self._key_store[row.fingerprint] = keydata
log.info('Add from row: %s %s', self.jid, row.fingerprint)
return keydata
def process_keylist(self, keylist):
log.info('Process keylist: %s %s', self.jid, keylist)
if keylist is None:
for keydata in self._key_store.values():
keydata.active = False
self._storage.save_contact(self.db_values())
return []
missing_pub_keys = []
fingerprints = set([key.fingerprint for key in keylist])
if fingerprints == self._key_store.keys():
log.info('No updates found')
for key in self._key_store.values():
if not key.has_pubkey:
missing_pub_keys.append(key.fingerprint)
return missing_pub_keys
for keydata in self._key_store.values():
keydata.active = False
for key in keylist:
try:
keydata = self._key_store[key.fingerprint]
keydata.active = True
if not keydata.has_pubkey:
missing_pub_keys.append(keydata.fingerprint)
except KeyError:
keydata = self.add_from_key(key)
missing_pub_keys.append(keydata.fingerprint)
self._storage.save_contact(self.db_values())
return missing_pub_keys
def set_public_key(self, fingerprint):
try:
keydata = self._key_store[fingerprint]
except KeyError:
log.warning('Set public key on unknown fingerprint: %s %s',
self.jid, fingerprint)
else:
keydata.has_pubkey = True
log.info('Set public key: %s %s', self.jid, fingerprint)
def get_keys(self, only_trusted=True):
keys = list(self._key_store.values())
if not only_trusted:
return keys
return [k for k in keys if k.active and k.trust in (Trust.VERIFIED,
Trust.BLIND)]
def get_key(self, fingerprint):
return self._key_store.get(fingerprint, None)
def set_trust(self, fingerprint, trust):
self._storage.set_trust(self.jid, fingerprint, trust)
def delete_key(self, fingerprint):
self._storage.delete_key(self.jid, fingerprint)
self._pgp.delete_key(fingerprint)
del self._key_store[fingerprint]
class PGPContacts:
'''
Holds all contacts available for PGP encryption
'''
def __init__(self, pgp, storage):
self._contacts = {}
self._storage = storage
self._pgp = pgp
self._load_from_storage()
self._load_from_keyring()
def _load_from_keyring(self):
log.info('Load keys from keyring')
keyring = self._pgp.get_keys()
for key in keyring:
log.info('Found: %s %s', key.jid, key.fingerprint)
self.set_public_key(key.jid, key.fingerprint)
def _load_from_storage(self):
log.info('Load contacts from storage')
rows = self._storage.load_contacts()
if rows is None:
return
for row in rows:
log.info('Found: %s %s', row.jid, row.fingerprint)
try:
contact_data = self._contacts[row.jid]
except KeyError:
contact_data = ContactData(row.jid, self._storage, self._pgp)
contact_data.add_from_db(row)
self._contacts[row.jid] = contact_data
else:
contact_data.add_from_db(row)
def process_keylist(self, jid, keylist):
try:
contact_data = self._contacts[jid]
except KeyError:
contact_data = ContactData(jid, self._storage, self._pgp)
missing_pub_keys = contact_data.process_keylist(keylist)
self._contacts[jid] = contact_data
else:
missing_pub_keys = contact_data.process_keylist(keylist)
return missing_pub_keys
def set_public_key(self, jid, fingerprint):
try:
contact_data = self._contacts[jid]
except KeyError:
log.warning('ContactData not found: %s %s', jid, fingerprint)
else:
contact_data.set_public_key(fingerprint)
def get_keys(self, jid, only_trusted=True):
try:
contact_data = self._contacts[jid]
return contact_data.get_keys(only_trusted=only_trusted)
except KeyError:
return []
def get_trust(self, jid, fingerprint):
contact_data = self._contacts.get(jid, None)
if contact_data is None:
return Trust.UNKNOWN
key = contact_data.get_key(fingerprint)
if key is None:
return Trust.UNKNOWN
return key.trust
This diff is collapsed.
# Copyright (C) 2018 Philipp Hörist <philipp AT hoerist.com>
#
# This file is part of Gajim.
#
# Gajim is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published
# by the Free Software Foundation; version 3 only.
#
# Gajim is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Gajim. If not, see <http://www.gnu.org/licenses/>.
# XEP-0373: OpenPGP for XMPP
import logging
import time
import nbxmpp
from gajim.common import app
from gajim.common.exceptions import StanzaMalformed
from gajim.common.modules.pep import AbstractPEPModule, AbstractPEPData
from gajim.common.modules.date_and_time import parse_datetime
from openpgp.modules import util
from openpgp.modules.util import Key
log = logging.getLogger('gajim.plugin_system.openpgp.pep')
# Module name
name = 'PGPKeylist'
zeroconf = False
class PGPKeylistData(AbstractPEPData):
type_ = 'openpgp-keylist'
class PGPKeylist(AbstractPEPModule):
'''
<item>
<public-keys-list xmlns='urn:xmpp:openpgp:0'>
<pubkey-metadata
v4-fingerprint='1357B01865B2503C18453D208CAC2A9678548E35'
date='2018-03-01T15:26:12Z'
/>
<pubkey-metadata
v4-fingerprint='67819B343B2AB70DED9320872C6464AF2A8E4C02'
date='1953-05-16T12:00:00Z'
/>
</public-keys-list>
</item>
'''
name = 'openpgp-keylist'
namespace = util.NS_OPENPGP_PUBLIC_KEYS
pep_class = PGPKeylistData
store_publish = True
_log = log
def _extract_info(self, item):
keylist_tag = item.getTag('public-keys-list',
namespace=util.NS_OPENPGP)
if keylist_tag is None:
raise StanzaMalformed('No public-keys-list node')
metadata = keylist_tag.getTags('pubkey-metadata')
if not metadata:
raise StanzaMalformed('No metadata found')
keylist = []
for data in metadata:
attrs = data.getAttrs()
if not attrs or 'v4-fingerprint' not in attrs:
raise StanzaMalformed('No fingerprint in metadata')
date = attrs.get('date', None)
if date is None:
raise StanzaMalformed('No date in metadata')
else:
timestamp = parse_datetime(date, epoch=True)
if timestamp is None:
raise StanzaMalformed('Invalid date timestamp: %s' % date)
keylist.append(Key(attrs['v4-fingerprint'], int(timestamp)))
return keylist
def _notification_received(self, jid, keylist):
con = app.connections[self._account]
con.get_module('OpenPGP').key_list_received(keylist.data,
jid.getStripped())
def _build_node(self, keylist):
keylist_node = nbxmpp.Node('public-keys-list',
{'xmlns': util.NS_OPENPGP})
if keylist is None:
return keylist_node
for key in keylist:
attrs = {'v4-fingerprint': key.fingerprint}
if key.date is not None:
date = time.strftime(
'%Y-%m-%dT%H:%M:%SZ', time.gmtime(key.date))
attrs['date'] = date
keylist_node.addChild('pubkey-metadata', attrs=attrs)
return keylist_node
def get_instance(*args, **kwargs):
return PGPKeylist(*args, **kwargs), 'PGPKeylist'
# Copyright (C) 2018 Philipp Hörist <philipp AT hoerist.com>
# Copyright (C) 2019 Philipp Hörist <philipp AT hoerist.com>
#
# This file is part of Gajim.