[openpgp] Refactor Plugin
- Adapt to nbxmpp now supporting openpgp
This commit is contained in:
@@ -1,20 +1,18 @@
|
|||||||
# 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
|
# it under the terms of the GNU General Public License as published
|
||||||
# by the Free Software Foundation; version 3 only.
|
# 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
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
# GNU General Public License for more details.
|
# GNU General Public License for more details.
|
||||||
#
|
#
|
||||||
# You should have received a copy of the GNU General Public License
|
# You should have received a copy of the GNU General Public License
|
||||||
# along with Gajim. If not, see <http://www.gnu.org/licenses/>.
|
# along with OpenPGP Gajim Plugin. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
# XEP-0373: OpenPGP for XMPP
|
|
||||||
|
|
||||||
|
|
||||||
import io
|
import io
|
||||||
|
|||||||
@@ -1,20 +1,18 @@
|
|||||||
# 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
|
# it under the terms of the GNU General Public License as published
|
||||||
# by the Free Software Foundation; version 3 only.
|
# 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
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
# GNU General Public License for more details.
|
# GNU General Public License for more details.
|
||||||
#
|
#
|
||||||
# You should have received a copy of the GNU General Public License
|
# You should have received a copy of the GNU General Public License
|
||||||
# along with Gajim. If not, see <http://www.gnu.org/licenses/>.
|
# along with OpenPGP Gajim Plugin. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
# XEP-0373: OpenPGP for XMPP
|
|
||||||
|
|
||||||
import os
|
import os
|
||||||
import logging
|
import logging
|
||||||
@@ -27,7 +25,6 @@ from gajim.common import app
|
|||||||
from openpgp.modules.util import DecryptionFailed
|
from openpgp.modules.util import DecryptionFailed
|
||||||
|
|
||||||
log = logging.getLogger('gajim.plugin_system.openpgp.pygnupg')
|
log = logging.getLogger('gajim.plugin_system.openpgp.pygnupg')
|
||||||
# gnupg.logger = log
|
|
||||||
|
|
||||||
KeyringItem = namedtuple('KeyringItem', 'jid keyid fingerprint')
|
KeyringItem = namedtuple('KeyringItem', 'jid keyid fingerprint')
|
||||||
|
|
||||||
@@ -38,7 +35,7 @@ class PGPContext(gnupg.GPG):
|
|||||||
self, gpgbinary=app.get_gpg_binary(), gnupghome=str(gnupghome))
|
self, gpgbinary=app.get_gpg_binary(), gnupghome=str(gnupghome))
|
||||||
|
|
||||||
self._passphrase = 'gajimopenpgppassphrase'
|
self._passphrase = 'gajimopenpgppassphrase'
|
||||||
self._jid = jid
|
self._jid = jid.getBare()
|
||||||
self._own_fingerprint = None
|
self._own_fingerprint = None
|
||||||
|
|
||||||
def _get_key_params(self, jid, passphrase):
|
def _get_key_params(self, jid, passphrase):
|
||||||
@@ -122,7 +119,7 @@ class PGPContext(gnupg.GPG):
|
|||||||
log.error(result.results[0])
|
log.error(result.results[0])
|
||||||
return
|
return
|
||||||
|
|
||||||
if not self.validate_key(data, jid):
|
if not self.validate_key(data, str(jid)):
|
||||||
return None
|
return None
|
||||||
key = self.get_key(result.results[0]['fingerprint'])
|
key = self.get_key(result.results[0]['fingerprint'])
|
||||||
return self._make_keyring_item(key[0])
|
return self._make_keyring_item(key[0])
|
||||||
|
|||||||
@@ -1,30 +1,30 @@
|
|||||||
# 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
|
# it under the terms of the GNU General Public License as published
|
||||||
# by the Free Software Foundation; version 3 only.
|
# 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
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
# GNU General Public License for more details.
|
# GNU General Public License for more details.
|
||||||
#
|
#
|
||||||
# You should have received a copy of the GNU General Public License
|
# You should have received a copy of the GNU General Public License
|
||||||
# along with Gajim. If not, see <http://www.gnu.org/licenses/>.
|
# along with OpenPGP Gajim Plugin. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
# XEP-0373: OpenPGP for XMPP
|
|
||||||
|
|
||||||
import sqlite3
|
import sqlite3
|
||||||
import logging
|
import logging
|
||||||
from collections import namedtuple
|
from collections import namedtuple
|
||||||
|
|
||||||
|
from nbxmpp import JID
|
||||||
|
|
||||||
log = logging.getLogger('gajim.plugin_system.openpgp.sql')
|
log = logging.getLogger('gajim.plugin_system.openpgp.sql')
|
||||||
|
|
||||||
TABLE_LAYOUT = '''
|
TABLE_LAYOUT = '''
|
||||||
CREATE TABLE contacts (
|
CREATE TABLE contacts (
|
||||||
jid TEXT,
|
jid JID,
|
||||||
fingerprint TEXT,
|
fingerprint TEXT,
|
||||||
active BOOLEAN,
|
active BOOLEAN,
|
||||||
trust INTEGER,
|
trust INTEGER,
|
||||||
@@ -34,10 +34,25 @@ TABLE_LAYOUT = '''
|
|||||||
CREATE UNIQUE INDEX jid_fingerprint ON contacts (jid, fingerprint);'''
|
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:
|
class Storage:
|
||||||
def __init__(self, folder_path):
|
def __init__(self, folder_path):
|
||||||
self._con = sqlite3.connect(str(folder_path / 'contacts.db'),
|
self._con = sqlite3.connect(str(folder_path / 'contacts.db'),
|
||||||
detect_types=sqlite3.PARSE_DECLTYPES)
|
detect_types=sqlite3.PARSE_DECLTYPES)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
self._con.row_factory = self._namedtuple_factory
|
self._con.row_factory = self._namedtuple_factory
|
||||||
self._create_database()
|
self._create_database()
|
||||||
self._migrate_database()
|
self._migrate_database()
|
||||||
@@ -72,10 +87,7 @@ class Storage:
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
def load_contacts(self):
|
def load_contacts(self):
|
||||||
sql = 'SELECT * from contacts'
|
return self._con.execute('SELECT * from contacts').fetchall()
|
||||||
rows = self._con.execute(sql).fetchall()
|
|
||||||
if rows is not None:
|
|
||||||
return rows
|
|
||||||
|
|
||||||
def save_contact(self, db_values):
|
def save_contact(self, db_values):
|
||||||
sql = '''REPLACE INTO
|
sql = '''REPLACE INTO
|
||||||
|
|||||||
@@ -1,20 +1,18 @@
|
|||||||
# 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
|
# it under the terms of the GNU General Public License as published
|
||||||
# by the Free Software Foundation; version 3 only.
|
# 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
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
# GNU General Public License for more details.
|
# GNU General Public License for more details.
|
||||||
#
|
#
|
||||||
# You should have received a copy of the GNU General Public License
|
# You should have received a copy of the GNU General Public License
|
||||||
# along with Gajim. If not, see <http://www.gnu.org/licenses/>.
|
# along with OpenPGP Gajim Plugin. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
# XEP-0373: OpenPGP for XMPP
|
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
import time
|
import time
|
||||||
@@ -24,7 +22,7 @@ from gi.repository import Gtk
|
|||||||
from gajim.common import app
|
from gajim.common import app
|
||||||
from gajim.common.const import DialogButton, ButtonAction
|
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 gajim.plugins.plugins_i18n import _
|
||||||
|
|
||||||
from openpgp.modules.util import Trust
|
from openpgp.modules.util import Trust
|
||||||
@@ -49,8 +47,8 @@ TRUST_DATA = {
|
|||||||
|
|
||||||
class KeyDialog(Gtk.Dialog):
|
class KeyDialog(Gtk.Dialog):
|
||||||
def __init__(self, account, jid, transient):
|
def __init__(self, account, jid, transient):
|
||||||
flags = Gtk.DialogFlags.DESTROY_WITH_PARENT
|
super().__init__(title=_('Public Keys for %s') % jid,
|
||||||
super().__init__(_('Public Keys for %s') % jid, None, flags)
|
destroy_with_parent=True)
|
||||||
|
|
||||||
self.set_transient_for(transient)
|
self.set_transient_for(transient)
|
||||||
self.set_resizable(True)
|
self.set_resizable(True)
|
||||||
|
|||||||
@@ -1,20 +1,18 @@
|
|||||||
# 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
|
# it under the terms of the GNU General Public License as published
|
||||||
# by the Free Software Foundation; version 3 only.
|
# 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
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
# GNU General Public License for more details.
|
# GNU General Public License for more details.
|
||||||
#
|
#
|
||||||
# You should have received a copy of the GNU General Public License
|
# You should have received a copy of the GNU General Public License
|
||||||
# along with Gajim. If not, see <http://www.gnu.org/licenses/>.
|
# along with OpenPGP Gajim Plugin. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
# XEP-0373: OpenPGP for XMPP
|
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
import threading
|
import threading
|
||||||
@@ -177,8 +175,8 @@ class NewKeyPage(RequestPage):
|
|||||||
error = e
|
error = e
|
||||||
else:
|
else:
|
||||||
self._con.get_module('OpenPGP').get_own_key_details()
|
self._con.get_module('OpenPGP').get_own_key_details()
|
||||||
self._con.get_module('OpenPGP').publish_key()
|
self._con.get_module('OpenPGP').set_public_key()
|
||||||
self._con.get_module('OpenPGP').query_key_list()
|
self._con.get_module('OpenPGP').request_keylist()
|
||||||
GLib.idle_add(self.finished, error)
|
GLib.idle_add(self.finished, error)
|
||||||
|
|
||||||
def finished(self, error):
|
def finished(self, error):
|
||||||
|
|||||||
257
openpgp/modules/key_store.py
Normal file
257
openpgp/modules/key_store.py
Normal file
@@ -0,0 +1,257 @@
|
|||||||
|
# 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
|
||||||
@@ -1,40 +1,47 @@
|
|||||||
# 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
|
# it under the terms of the GNU General Public License as published
|
||||||
# by the Free Software Foundation; version 3 only.
|
# 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
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
# GNU General Public License for more details.
|
# GNU General Public License for more details.
|
||||||
#
|
#
|
||||||
# You should have received a copy of the GNU General Public License
|
# You should have received a copy of the GNU General Public License
|
||||||
# along with Gajim. If not, see <http://www.gnu.org/licenses/>.
|
# along with OpenPGP Gajim Plugin. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
# XEP-0373: OpenPGP for XMPP
|
|
||||||
|
|
||||||
import time
|
import time
|
||||||
import logging
|
import logging
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from base64 import b64decode, b64encode
|
|
||||||
|
|
||||||
from nbxmpp import Node, isResultNode
|
import nbxmpp
|
||||||
|
from nbxmpp import Node
|
||||||
|
from nbxmpp import StanzaMalformed
|
||||||
|
from nbxmpp.util import is_error_result
|
||||||
|
from nbxmpp.structs import StanzaHandler
|
||||||
|
from nbxmpp.modules.openpgp import PGPKeyMetadata
|
||||||
|
from nbxmpp.modules.openpgp import parse_signcrypt
|
||||||
|
from nbxmpp.modules.openpgp import create_signcrypt_node
|
||||||
|
from nbxmpp.modules.openpgp import create_message_stanza
|
||||||
|
|
||||||
from gajim.common import app
|
from gajim.common import app
|
||||||
from gajim.common import configpaths
|
from gajim.common import configpaths
|
||||||
from gajim.common.nec import NetworkEvent
|
from gajim.common.nec import NetworkEvent
|
||||||
|
from gajim.common.const import EncryptionData
|
||||||
|
from gajim.common.modules.base import BaseModule
|
||||||
|
from gajim.common.modules.util import event_node
|
||||||
|
|
||||||
from openpgp.modules import util
|
|
||||||
from openpgp.modules.util import ENCRYPTION_NAME
|
from openpgp.modules.util import ENCRYPTION_NAME
|
||||||
from openpgp.modules.util import add_additional_data
|
from openpgp.modules.util import NOT_ENCRYPTED_TAGS
|
||||||
from openpgp.modules.util import NS_OPENPGP_PUBLIC_KEYS
|
|
||||||
from openpgp.modules.util import NS_OPENPGP
|
|
||||||
from openpgp.modules.util import Key
|
from openpgp.modules.util import Key
|
||||||
from openpgp.modules.util import Trust
|
from openpgp.modules.util import add_additional_data
|
||||||
from openpgp.modules.util import DecryptionFailed
|
from openpgp.modules.util import DecryptionFailed
|
||||||
|
from openpgp.modules.util import prepare_stanza
|
||||||
|
from openpgp.modules.key_store import PGPContacts
|
||||||
from openpgp.backend.sql import Storage
|
from openpgp.backend.sql import Storage
|
||||||
from openpgp.backend.pygpg import PGPContext
|
from openpgp.backend.pygpg import PGPContext
|
||||||
|
|
||||||
@@ -47,252 +54,34 @@ name = ENCRYPTION_NAME
|
|||||||
zeroconf = False
|
zeroconf = False
|
||||||
|
|
||||||
|
|
||||||
class KeyData:
|
class OpenPGP(BaseModule):
|
||||||
'''
|
|
||||||
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
|
_nbxmpp_extends = 'OpenPGP'
|
||||||
def trust(self):
|
_nbxmpp_methods = [
|
||||||
return self._trust
|
'set_keylist',
|
||||||
|
'request_keylist',
|
||||||
|
'set_public_key',
|
||||||
|
'request_public_key',
|
||||||
|
'set_secret_key',
|
||||||
|
'request_secret_key',
|
||||||
|
]
|
||||||
|
|
||||||
@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
|
|
||||||
|
|
||||||
|
|
||||||
class OpenPGP:
|
|
||||||
def __init__(self, con):
|
def __init__(self, con):
|
||||||
self._con = con
|
BaseModule.__init__(self, con)
|
||||||
self._account = con.name
|
|
||||||
|
|
||||||
self.handlers = []
|
self.handlers = [
|
||||||
|
StanzaHandler(name='message',
|
||||||
|
callback=self.decrypt_message,
|
||||||
|
ns=nbxmpp.NS_OPENPGP,
|
||||||
|
priority=9),
|
||||||
|
]
|
||||||
|
|
||||||
self.own_jid = self.get_own_jid(stripped=True)
|
self._register_pubsub_handler(self._keylist_notification_received)
|
||||||
|
|
||||||
path = Path(configpaths.get('MY_DATA')) / 'openpgp' / self.own_jid
|
self.own_jid = self._con.get_own_jid()
|
||||||
|
|
||||||
|
own_bare_jid = self.own_jid.getBare()
|
||||||
|
path = Path(configpaths.get('MY_DATA')) / 'openpgp' / own_bare_jid
|
||||||
if not path.exists():
|
if not path.exists():
|
||||||
path.mkdir(parents=True)
|
path.mkdir(parents=True)
|
||||||
|
|
||||||
@@ -306,11 +95,6 @@ class OpenPGP:
|
|||||||
def secret_key_available(self):
|
def secret_key_available(self):
|
||||||
return self._fingerprint is not None
|
return self._fingerprint is not None
|
||||||
|
|
||||||
def get_own_jid(self, stripped=False):
|
|
||||||
if stripped:
|
|
||||||
return self._con.get_own_jid().getStripped()
|
|
||||||
return self._con.get_own_jid()
|
|
||||||
|
|
||||||
def get_own_key_details(self):
|
def get_own_key_details(self):
|
||||||
self._fingerprint, self._date = self._pgp.get_own_key_details()
|
self._fingerprint, self._date = self._pgp.get_own_key_details()
|
||||||
return self._fingerprint, self._date
|
return self._fingerprint, self._date
|
||||||
@@ -318,105 +102,77 @@ class OpenPGP:
|
|||||||
def generate_key(self):
|
def generate_key(self):
|
||||||
self._pgp.generate_key()
|
self._pgp.generate_key()
|
||||||
|
|
||||||
def publish_key(self):
|
def set_public_key(self):
|
||||||
log.info('%s => Publish key', self._account)
|
log.info('%s => Publish public key', self._account)
|
||||||
key = self._pgp.export_key(self._fingerprint)
|
key = self._pgp.export_key(self._fingerprint)
|
||||||
|
self._nbxmpp('OpenPGP').set_public_key(
|
||||||
|
key, self._fingerprint, self._date)
|
||||||
|
|
||||||
date = time.strftime(
|
def request_public_key(self, jid, fingerprint):
|
||||||
'%Y-%m-%dT%H:%M:%SZ', time.gmtime(self._date))
|
log.info('%s => Request public key %s - %s',
|
||||||
pubkey_node = Node('pubkey', attrs={'xmlns': NS_OPENPGP,
|
|
||||||
'date': date})
|
|
||||||
data = pubkey_node.addChild('data')
|
|
||||||
data.addData(b64encode(key).decode('utf8'))
|
|
||||||
node = '%s:%s' % (NS_OPENPGP_PUBLIC_KEYS, self._fingerprint)
|
|
||||||
|
|
||||||
self._con.get_module('PubSub').send_pb_publish(
|
|
||||||
self.own_jid, node, pubkey_node,
|
|
||||||
id_='current', cb=self._public_result)
|
|
||||||
|
|
||||||
def _publish_key_list(self, keylist=None):
|
|
||||||
if keylist is None:
|
|
||||||
keylist = [Key(self._fingerprint, self._date)]
|
|
||||||
log.info('%s => Publish keys list', self._account)
|
|
||||||
self._con.get_module('PGPKeylist').send(keylist)
|
|
||||||
|
|
||||||
def _public_result(self, con, stanza):
|
|
||||||
if not isResultNode(stanza):
|
|
||||||
log.error('%s => Publishing failed: %s',
|
|
||||||
self._account, stanza.getError())
|
|
||||||
|
|
||||||
def _query_public_key(self, jid, fingerprint):
|
|
||||||
log.info('%s => Fetch public key %s - %s',
|
|
||||||
self._account, fingerprint, jid)
|
self._account, fingerprint, jid)
|
||||||
node = '%s:%s' % (NS_OPENPGP_PUBLIC_KEYS, fingerprint)
|
self._nbxmpp('OpenPGP').request_public_key(
|
||||||
self._con.get_module('PubSub').send_pb_retrieve(
|
jid,
|
||||||
jid, node, cb=self._public_key_received, fingerprint=fingerprint)
|
fingerprint,
|
||||||
|
callback=self._public_key_received,
|
||||||
|
user_data=fingerprint)
|
||||||
|
|
||||||
def _public_key_received(self, con, stanza, fingerprint):
|
def _public_key_received(self, result, fingerprint):
|
||||||
if not isResultNode(stanza):
|
if is_error_result(result):
|
||||||
log.error('%s => Public Key not found: %s',
|
log.error('%s => Public Key not found: %s',
|
||||||
self._account, stanza.getError())
|
self._account, result)
|
||||||
return
|
|
||||||
pubkey = util.unpack_public_key(stanza, fingerprint)
|
|
||||||
if pubkey is None:
|
|
||||||
log.warning('Invalid public key received:\n%s', stanza)
|
|
||||||
return
|
return
|
||||||
|
|
||||||
jid = stanza.getFrom().getStripped()
|
imported_key = self._pgp.import_key(result.key, result.jid)
|
||||||
result = self._pgp.import_key(pubkey, jid)
|
if imported_key is not None:
|
||||||
if result is not None:
|
self._contacts.set_public_key(result.jid, fingerprint)
|
||||||
self._contacts.set_public_key(jid, fingerprint)
|
|
||||||
|
|
||||||
def query_key_list(self, jid=None):
|
def set_keylist(self, keylist=None):
|
||||||
|
if keylist is None:
|
||||||
|
keylist = [PGPKeyMetadata(None, self._fingerprint, self._date)]
|
||||||
|
log.info('%s => Publish keylist', self._account)
|
||||||
|
self._nbxmpp('OpenPGP').set_keylist(keylist)
|
||||||
|
|
||||||
|
@event_node(nbxmpp.NS_OPENPGP_PK)
|
||||||
|
def _keylist_notification_received(self, _con, _stanza, properties):
|
||||||
|
keylist = []
|
||||||
|
if not properties.pubsub_event.empty:
|
||||||
|
keylist = properties.pubsub_event.data
|
||||||
|
|
||||||
|
self._process_keylist(keylist, properties.jid)
|
||||||
|
|
||||||
|
def request_keylist(self, jid=None):
|
||||||
if jid is None:
|
if jid is None:
|
||||||
jid = self.own_jid
|
jid = self.own_jid
|
||||||
log.info('%s => Fetch keys list %s', self._account, jid)
|
log.info('%s => Fetch keylist %s', self._account, jid)
|
||||||
self._con.get_module('PubSub').send_pb_retrieve(
|
|
||||||
jid, NS_OPENPGP_PUBLIC_KEYS,
|
|
||||||
cb=self._query_key_list_result)
|
|
||||||
|
|
||||||
def _query_key_list_result(self, con, stanza):
|
self._nbxmpp('OpenPGP').request_keylist(
|
||||||
from_jid = stanza.getFrom()
|
jid,
|
||||||
if from_jid is None:
|
callback=self._keylist_received,
|
||||||
from_jid = self.own_jid
|
user_data=jid)
|
||||||
else:
|
|
||||||
from_jid = from_jid.getStripped()
|
|
||||||
|
|
||||||
if not isResultNode(stanza):
|
def _keylist_received(self, result, jid):
|
||||||
log.error('%s => Keys list query failed: %s',
|
if is_error_result(result):
|
||||||
self._account, stanza.getError())
|
log.error('%s => Keylist query failed: %s',
|
||||||
if from_jid == self.own_jid and self._fingerprint is not None:
|
self._account, result)
|
||||||
self._publish_key_list()
|
if self.own_jid.bareMatch(jid) and self._fingerprint is not None:
|
||||||
|
self.set_keylist()
|
||||||
return
|
return
|
||||||
|
|
||||||
from_jid = stanza.getFrom()
|
log.info('Keylist received from %s', jid)
|
||||||
if from_jid is None:
|
self._process_keylist(result, jid)
|
||||||
from_jid = self.own_jid
|
|
||||||
else:
|
|
||||||
from_jid = from_jid.getStripped()
|
|
||||||
|
|
||||||
log.info('Key list query received from %s', from_jid)
|
|
||||||
|
|
||||||
keylist = util.unpack_public_key_list(stanza, from_jid)
|
|
||||||
self.key_list_received(keylist, from_jid)
|
|
||||||
|
|
||||||
def key_list_received(self, keylist, from_jid):
|
|
||||||
if keylist is None:
|
|
||||||
log.warning('Invalid keys list received')
|
|
||||||
if from_jid == self.own_jid and self._fingerprint is not None:
|
|
||||||
self._publish_key_list()
|
|
||||||
return
|
|
||||||
|
|
||||||
|
def _process_keylist(self, keylist, from_jid):
|
||||||
if not keylist:
|
if not keylist:
|
||||||
log.warning('%s => Empty keys list received from %s',
|
log.warning('%s => Empty keylist received from %s',
|
||||||
self._account, from_jid)
|
self._account, from_jid)
|
||||||
self._contacts.process_keylist(self.own_jid, keylist)
|
self._contacts.process_keylist(self.own_jid, keylist)
|
||||||
if from_jid == self.own_jid and self._fingerprint is not None:
|
if self.own_jid.bareMatch(from_jid) and self._fingerprint is not None:
|
||||||
self._publish_key_list()
|
self.set_keylist()
|
||||||
return
|
return
|
||||||
|
|
||||||
if from_jid == self.own_jid:
|
if self.own_jid.bareMatch(from_jid):
|
||||||
log.info('Received own keys list')
|
log.info('Received own keylist')
|
||||||
for key in keylist:
|
for key in keylist:
|
||||||
log.info(key.fingerprint)
|
log.info(key.fingerprint)
|
||||||
for key in keylist:
|
for key in keylist:
|
||||||
@@ -427,7 +183,7 @@ class OpenPGP:
|
|||||||
log.info('Own key not published')
|
log.info('Own key not published')
|
||||||
if self._fingerprint is not None:
|
if self._fingerprint is not None:
|
||||||
keylist.append(Key(self._fingerprint, self._date))
|
keylist.append(Key(self._fingerprint, self._date))
|
||||||
self._publish_key_list(keylist)
|
self.set_keylist(keylist)
|
||||||
return
|
return
|
||||||
|
|
||||||
missing_pub_keys = self._contacts.process_keylist(from_jid, keylist)
|
missing_pub_keys = self._contacts.process_keylist(from_jid, keylist)
|
||||||
@@ -436,83 +192,48 @@ class OpenPGP:
|
|||||||
log.info(key.fingerprint)
|
log.info(key.fingerprint)
|
||||||
|
|
||||||
for fingerprint in missing_pub_keys:
|
for fingerprint in missing_pub_keys:
|
||||||
self._query_public_key(from_jid, fingerprint)
|
self.request_public_key(from_jid, fingerprint)
|
||||||
|
|
||||||
def decrypt_message(self, obj, callback):
|
def decrypt_message(self, _con, stanza, properties):
|
||||||
if obj.encrypted:
|
if not properties.is_openpgp:
|
||||||
# Another Plugin already decrypted the message
|
|
||||||
return
|
return
|
||||||
|
|
||||||
if obj.name == 'message-received':
|
|
||||||
enc_tag = obj.stanza.getTag('openpgp', namespace=NS_OPENPGP)
|
|
||||||
jid = obj.jid
|
|
||||||
else:
|
|
||||||
enc_tag = obj.message.getTag('openpgp', namespace=NS_OPENPGP)
|
|
||||||
jid = obj.with_
|
|
||||||
|
|
||||||
if enc_tag is None:
|
|
||||||
return
|
|
||||||
|
|
||||||
log.info('Received OpenPGP message from: %s', jid)
|
|
||||||
b64encode_payload = enc_tag.getData()
|
|
||||||
encrypted_payload = b64decode(b64encode_payload)
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
decrypted_payload, fingerprint = self._pgp.decrypt(
|
payload, fingerprint = self._pgp.decrypt(properties.openpgp)
|
||||||
encrypted_payload)
|
|
||||||
except DecryptionFailed as error:
|
except DecryptionFailed as error:
|
||||||
log.warning(error)
|
log.warning(error)
|
||||||
return
|
return
|
||||||
|
|
||||||
signcrypt = Node(node=decrypted_payload)
|
signcrypt = Node(node=payload)
|
||||||
|
|
||||||
signcrypt_jid = signcrypt.getTagAttr('to', 'jid')
|
try:
|
||||||
if self.own_jid != signcrypt_jid:
|
payload, to, timestamp = parse_signcrypt(signcrypt)
|
||||||
log.warning('signcrypt "to" attr %s != %s',
|
except StanzaMalformed as error:
|
||||||
self.own_jid, signcrypt_jid)
|
log.warning('Decryption failed: %s', error)
|
||||||
log.debug(signcrypt)
|
log.warning(payload)
|
||||||
return
|
return
|
||||||
|
|
||||||
payload = signcrypt.getTag('payload')
|
if not self.own_jid.bareMatch(to):
|
||||||
|
log.warning('to attr not valid')
|
||||||
|
log.warning(payload)
|
||||||
|
return
|
||||||
|
|
||||||
body = None
|
log.info('Received OpenPGP message from: %s', properties.jid)
|
||||||
if obj.name == 'message-received':
|
prepare_stanza(stanza, payload)
|
||||||
obj.stanza.delChild(enc_tag)
|
|
||||||
for node in payload.getChildren():
|
|
||||||
if node.name == 'body':
|
|
||||||
body = node.getData()
|
|
||||||
obj.stanza.setTagData('body', body)
|
|
||||||
else:
|
|
||||||
obj.stanza.addChild(node=node)
|
|
||||||
else:
|
|
||||||
obj.msg_.delChild(enc_tag)
|
|
||||||
for node in payload.getChildren():
|
|
||||||
if node.name == 'body':
|
|
||||||
body = node.getData()
|
|
||||||
obj.msg_.setTagData('body', node.getData())
|
|
||||||
else:
|
|
||||||
obj.msg_.addChild(node=node)
|
|
||||||
|
|
||||||
if body:
|
properties.encrypted = EncryptionData({'name': ENCRYPTION_NAME,
|
||||||
obj.msgtxt = body
|
'fingerprint': fingerprint})
|
||||||
|
|
||||||
add_additional_data(obj.additional_data,
|
|
||||||
fingerprint)
|
|
||||||
|
|
||||||
obj.encrypted = ENCRYPTION_NAME
|
|
||||||
callback(obj)
|
|
||||||
|
|
||||||
def encrypt_message(self, obj, callback):
|
def encrypt_message(self, obj, callback):
|
||||||
keys = self._contacts.get_keys(obj.jid)
|
keys = self._contacts.get_keys(obj.jid)
|
||||||
if not keys:
|
if not keys:
|
||||||
# TODO: this should never happen in theory
|
|
||||||
log.error('Droping stanza to %s, because we have no key', obj.jid)
|
log.error('Droping stanza to %s, because we have no key', obj.jid)
|
||||||
return
|
return
|
||||||
|
|
||||||
keys += self._contacts.get_keys(self.own_jid)
|
keys += self._contacts.get_keys(self.own_jid)
|
||||||
keys += [Key(self._fingerprint, None)]
|
keys += [Key(self._fingerprint, None)]
|
||||||
|
|
||||||
payload = util.create_signcrypt_node(obj)
|
payload = create_signcrypt_node(obj.msg_iq, NOT_ENCRYPTED_TAGS)
|
||||||
|
|
||||||
encrypted_payload, error = self._pgp.encrypt(payload, keys)
|
encrypted_payload, error = self._pgp.encrypt(payload, keys)
|
||||||
if error:
|
if error:
|
||||||
@@ -523,16 +244,15 @@ class OpenPGP:
|
|||||||
jid=obj.jid,
|
jid=obj.jid,
|
||||||
message=obj.message,
|
message=obj.message,
|
||||||
error=error,
|
error=error,
|
||||||
time_=time.time()))
|
time_=time.time(),
|
||||||
|
session=None))
|
||||||
return
|
return
|
||||||
|
|
||||||
util.create_openpgp_message(obj, encrypted_payload)
|
create_message_stanza(obj.msg_iq, encrypted_payload, bool(obj.message))
|
||||||
|
|
||||||
add_additional_data(obj.additional_data,
|
add_additional_data(obj.additional_data,
|
||||||
self._fingerprint)
|
self._fingerprint)
|
||||||
|
|
||||||
obj.encrypted = ENCRYPTION_NAME
|
obj.encrypted = ENCRYPTION_NAME
|
||||||
self.print_msg_to_log(obj.msg_iq)
|
|
||||||
callback(obj)
|
callback(obj)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
@@ -550,7 +270,7 @@ class OpenPGP:
|
|||||||
return self._contacts.get_keys(jid, only_trusted=only_trusted)
|
return self._contacts.get_keys(jid, only_trusted=only_trusted)
|
||||||
|
|
||||||
def clear_fingerprints(self):
|
def clear_fingerprints(self):
|
||||||
self._publish_key_list()
|
self.set_keylist()
|
||||||
|
|
||||||
def cleanup(self):
|
def cleanup(self):
|
||||||
self._storage.cleanup()
|
self._storage.cleanup()
|
||||||
|
|||||||
@@ -1,116 +0,0 @@
|
|||||||
# 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'
|
|
||||||
@@ -1,49 +1,38 @@
|
|||||||
# 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
|
# it under the terms of the GNU General Public License as published
|
||||||
# by the Free Software Foundation; version 3 only.
|
# 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
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
# GNU General Public License for more details.
|
# GNU General Public License for more details.
|
||||||
#
|
#
|
||||||
# You should have received a copy of the GNU General Public License
|
# You should have received a copy of the GNU General Public License
|
||||||
# along with Gajim. If not, see <http://www.gnu.org/licenses/>.
|
# along with OpenPGP Gajim Plugin. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
# XEP-0373: OpenPGP for XMPP
|
|
||||||
|
|
||||||
import logging
|
|
||||||
import random
|
|
||||||
import time
|
|
||||||
import string
|
|
||||||
from enum import IntEnum
|
from enum import IntEnum
|
||||||
from collections import namedtuple
|
from collections import namedtuple
|
||||||
from base64 import b64decode, b64encode
|
|
||||||
|
|
||||||
import nbxmpp
|
import nbxmpp
|
||||||
from nbxmpp import Node
|
|
||||||
|
|
||||||
from gajim.common.modules.date_and_time import parse_datetime
|
|
||||||
|
|
||||||
ENCRYPTION_NAME = 'OpenPGP'
|
ENCRYPTION_NAME = 'OpenPGP'
|
||||||
NS_OPENPGP = 'urn:xmpp:openpgp:0'
|
|
||||||
NS_OPENPGP_PUBLIC_KEYS = 'urn:xmpp:openpgp:0:public-keys'
|
|
||||||
NS_NOTIFY = NS_OPENPGP_PUBLIC_KEYS + '+notify'
|
|
||||||
|
|
||||||
NOT_ENCRYPTED_TAGS = [('no-store', nbxmpp.NS_MSG_HINTS),
|
NOT_ENCRYPTED_TAGS = [
|
||||||
('store', nbxmpp.NS_MSG_HINTS),
|
('no-store', nbxmpp.NS_MSG_HINTS),
|
||||||
('no-copy', nbxmpp.NS_MSG_HINTS),
|
('store', nbxmpp.NS_MSG_HINTS),
|
||||||
('no-permanent-store', nbxmpp.NS_MSG_HINTS),
|
('no-copy', nbxmpp.NS_MSG_HINTS),
|
||||||
('thread', None)]
|
('no-permanent-store', nbxmpp.NS_MSG_HINTS),
|
||||||
|
('origin-id', nbxmpp.NS_SID),
|
||||||
|
('thread', None)
|
||||||
|
]
|
||||||
|
|
||||||
Key = namedtuple('Key', 'fingerprint date')
|
Key = namedtuple('Key', 'fingerprint date')
|
||||||
|
|
||||||
log = logging.getLogger('gajim.plugin_system.openpgp.util')
|
|
||||||
|
|
||||||
|
|
||||||
class Trust(IntEnum):
|
class Trust(IntEnum):
|
||||||
NOT_TRUSTED = 0
|
NOT_TRUSTED = 0
|
||||||
@@ -52,159 +41,25 @@ class Trust(IntEnum):
|
|||||||
VERIFIED = 3
|
VERIFIED = 3
|
||||||
|
|
||||||
|
|
||||||
def unpack_public_key_list(stanza, from_jid):
|
def prepare_stanza(stanza, payload):
|
||||||
fingerprints = []
|
delete_nodes(stanza, 'openpgp', nbxmpp.NS_OPENPGP)
|
||||||
|
delete_nodes(stanza, 'body')
|
||||||
|
|
||||||
parent = stanza.getTag('pubsub', namespace=nbxmpp.NS_PUBSUB)
|
nodes = [(node.getName(), node.getNamespace()) for node in payload]
|
||||||
if parent is None:
|
for name, namespace in nodes:
|
||||||
parent = stanza.getTag('event', namespace=nbxmpp.NS_PUBSUB_EVENT)
|
delete_nodes(stanza, name, namespace)
|
||||||
if parent is None:
|
|
||||||
log.warning('PGP keys list has no pubsub/event node')
|
|
||||||
return
|
|
||||||
|
|
||||||
items = parent.getTag('items', attrs={'node': NS_OPENPGP_PUBLIC_KEYS})
|
for node in payload:
|
||||||
if items is None:
|
stanza.addChild(node=node)
|
||||||
log.warning('PGP keys list has no items node')
|
|
||||||
return
|
|
||||||
|
|
||||||
item = items.getTags('item')
|
|
||||||
if not item:
|
|
||||||
log.warning('PGP keys list has no item node')
|
|
||||||
return
|
|
||||||
|
|
||||||
if len(item) > 1:
|
|
||||||
log.warning('PGP keys list has more than one item')
|
|
||||||
return
|
|
||||||
|
|
||||||
key_list = item[0].getTag('public-keys-list', namespace=NS_OPENPGP)
|
|
||||||
if key_list is None:
|
|
||||||
log.warning('PGP keys list has no public-keys-list node')
|
|
||||||
return
|
|
||||||
|
|
||||||
metadata = key_list.getTags('pubkey-metadata')
|
|
||||||
if not metadata:
|
|
||||||
return []
|
|
||||||
|
|
||||||
for node in metadata:
|
|
||||||
attrs = node.getAttrs()
|
|
||||||
if 'v4-fingerprint' not in attrs:
|
|
||||||
log.warning('No fingerprint in metadata node')
|
|
||||||
return
|
|
||||||
|
|
||||||
date = attrs.get('date', None)
|
|
||||||
if date is None:
|
|
||||||
log.warning('No date in metadata')
|
|
||||||
return
|
|
||||||
|
|
||||||
timestamp = parse_datetime(date, epoch=True)
|
|
||||||
if timestamp is None:
|
|
||||||
log.warning('Invalid date timestamp: %s', date)
|
|
||||||
return
|
|
||||||
|
|
||||||
fingerprints.append(
|
|
||||||
Key(attrs['v4-fingerprint'], int(timestamp)))
|
|
||||||
|
|
||||||
return fingerprints
|
|
||||||
|
|
||||||
|
|
||||||
def unpack_public_key(stanza, fingerprint):
|
def delete_nodes(stanza, name, namespace=None):
|
||||||
pubsub = stanza.getTag('pubsub', namespace=nbxmpp.NS_PUBSUB)
|
attrs = None
|
||||||
if pubsub is None:
|
if namespace is not None:
|
||||||
log.warning('PGP public key has no pubsub node')
|
attrs = {'xmlns': nbxmpp.NS_OPENPGP}
|
||||||
return
|
nodes = stanza.getTags(name, attrs)
|
||||||
node = '%s:%s' % (NS_OPENPGP_PUBLIC_KEYS, fingerprint)
|
for node in nodes:
|
||||||
items = pubsub.getTag('items', attrs={'node': node})
|
stanza.delChild(node)
|
||||||
if items is None:
|
|
||||||
log.warning('PGP public key has no items node')
|
|
||||||
return
|
|
||||||
|
|
||||||
item = items.getTags('item')
|
|
||||||
if not item:
|
|
||||||
log.warning('PGP public key has no item node')
|
|
||||||
return
|
|
||||||
|
|
||||||
if len(item) > 1:
|
|
||||||
log.warning('PGP public key has more than one item')
|
|
||||||
return
|
|
||||||
|
|
||||||
pub_key = item[0].getTag('pubkey', namespace=NS_OPENPGP)
|
|
||||||
if pub_key is None:
|
|
||||||
log.warning('PGP public key has no pubkey node')
|
|
||||||
return
|
|
||||||
|
|
||||||
data = pub_key.getTag('data')
|
|
||||||
if data is None:
|
|
||||||
log.warning('PGP public key has no data node')
|
|
||||||
return
|
|
||||||
|
|
||||||
return b64decode(data.getData().encode('utf8'))
|
|
||||||
|
|
||||||
|
|
||||||
def create_signcrypt_node(obj):
|
|
||||||
'''
|
|
||||||
<signcrypt xmlns='urn:xmpp:openpgp:0'>
|
|
||||||
<to jid='juliet@example.org'/>
|
|
||||||
<time stamp='2014-07-10T17:06:00+02:00'/>
|
|
||||||
<rpad>
|
|
||||||
f0rm1l4n4-mT8y33j!Y%fRSrcd^ZE4Q7VDt1L%WEgR!kv
|
|
||||||
</rpad>
|
|
||||||
<payload>
|
|
||||||
<body xmlns='jabber:client'>
|
|
||||||
This is a secret message.
|
|
||||||
</body>
|
|
||||||
</payload>
|
|
||||||
</signcrypt>
|
|
||||||
'''
|
|
||||||
|
|
||||||
encrypted_nodes = []
|
|
||||||
child_nodes = obj.msg_iq.getChildren()
|
|
||||||
for node in child_nodes:
|
|
||||||
if (node.name, node.namespace) not in NOT_ENCRYPTED_TAGS:
|
|
||||||
if not node.namespace:
|
|
||||||
node.setNamespace(nbxmpp.NS_CLIENT)
|
|
||||||
encrypted_nodes.append(node)
|
|
||||||
obj.msg_iq.delChild(node)
|
|
||||||
|
|
||||||
signcrypt = Node('signcrypt', attrs={'xmlns': NS_OPENPGP})
|
|
||||||
signcrypt.addChild('to', attrs={'jid': obj.jid})
|
|
||||||
|
|
||||||
timestamp = time.strftime('%Y-%m-%dT%H:%M:%SZ', time.gmtime())
|
|
||||||
signcrypt.addChild('time', attrs={'stamp': timestamp})
|
|
||||||
|
|
||||||
signcrypt.addChild('rpad').addData(get_rpad())
|
|
||||||
|
|
||||||
payload = signcrypt.addChild('payload')
|
|
||||||
|
|
||||||
for node in encrypted_nodes:
|
|
||||||
payload.addChild(node=node)
|
|
||||||
|
|
||||||
return signcrypt
|
|
||||||
|
|
||||||
|
|
||||||
def get_rpad():
|
|
||||||
rpad_range = random.randint(30, 50)
|
|
||||||
return ''.join(
|
|
||||||
random.choice(string.ascii_letters) for _ in range(rpad_range))
|
|
||||||
|
|
||||||
|
|
||||||
def create_openpgp_message(obj, encrypted_payload):
|
|
||||||
b64encoded_payload = b64encode(encrypted_payload).decode('utf8')
|
|
||||||
|
|
||||||
openpgp_node = nbxmpp.Node('openpgp', attrs={'xmlns': NS_OPENPGP})
|
|
||||||
openpgp_node.addData(b64encoded_payload)
|
|
||||||
obj.msg_iq.addChild(node=openpgp_node)
|
|
||||||
|
|
||||||
eme_node = nbxmpp.Node('encryption',
|
|
||||||
attrs={'xmlns': nbxmpp.NS_EME,
|
|
||||||
'namespace': NS_OPENPGP})
|
|
||||||
obj.msg_iq.addChild(node=eme_node)
|
|
||||||
|
|
||||||
if obj.message:
|
|
||||||
obj.msg_iq.setBody(get_info_message())
|
|
||||||
|
|
||||||
|
|
||||||
def get_info_message():
|
|
||||||
return '[This message is *encrypted* with OpenPGP (See :XEP:`0373`]'
|
|
||||||
|
|
||||||
|
|
||||||
def add_additional_data(data, fingerprint):
|
def add_additional_data(data, fingerprint):
|
||||||
|
|||||||
@@ -1,20 +1,18 @@
|
|||||||
# 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
|
# it under the terms of the GNU General Public License as published
|
||||||
# by the Free Software Foundation; version 3 only.
|
# 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
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
# GNU General Public License for more details.
|
# GNU General Public License for more details.
|
||||||
#
|
#
|
||||||
# You should have received a copy of the GNU General Public License
|
# You should have received a copy of the GNU General Public License
|
||||||
# along with Gajim. If not, see <http://www.gnu.org/licenses/>.
|
# along with OpenPGP Gajim Plugin. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
# XEP-0373: OpenPGP for XMPP
|
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
@@ -22,19 +20,21 @@ from pathlib import Path
|
|||||||
|
|
||||||
from gi.repository import Gtk
|
from gi.repository import Gtk
|
||||||
from gi.repository import Gdk
|
from gi.repository import Gdk
|
||||||
|
import nbxmpp
|
||||||
|
from nbxmpp import JID
|
||||||
|
|
||||||
from gajim.plugins import GajimPlugin
|
|
||||||
from gajim.common import app
|
from gajim.common import app
|
||||||
from gajim.common import ged
|
from gajim.common import ged
|
||||||
from gajim.common import configpaths
|
from gajim.common import configpaths
|
||||||
from gajim.common import helpers
|
from gajim.common import helpers
|
||||||
from gajim.common.const import CSSPriority
|
from gajim.common.const import CSSPriority
|
||||||
|
|
||||||
from gajim.gtk.dialogs import ErrorDialog
|
from gajim.gtk.dialogs import ErrorDialog
|
||||||
|
|
||||||
|
from gajim.plugins import GajimPlugin
|
||||||
from gajim.plugins.plugins_i18n import _
|
from gajim.plugins.plugins_i18n import _
|
||||||
|
|
||||||
from openpgp.modules.util import NS_NOTIFY
|
|
||||||
from openpgp.modules.util import ENCRYPTION_NAME
|
from openpgp.modules.util import ENCRYPTION_NAME
|
||||||
from openpgp.modules import pgp_keylist
|
|
||||||
try:
|
try:
|
||||||
from openpgp.modules import openpgp
|
from openpgp.modules import openpgp
|
||||||
except ImportError as e:
|
except ImportError as e:
|
||||||
@@ -45,8 +45,6 @@ else:
|
|||||||
log = logging.getLogger('gajim.plugin_system.openpgp')
|
log = logging.getLogger('gajim.plugin_system.openpgp')
|
||||||
|
|
||||||
|
|
||||||
#TODO: we cant encrypt "thread" right now, because its needed for Gajim to find ChatControls.
|
|
||||||
|
|
||||||
class OpenPGPPlugin(GajimPlugin):
|
class OpenPGPPlugin(GajimPlugin):
|
||||||
def init(self):
|
def init(self):
|
||||||
if ERROR_MSG:
|
if ERROR_MSG:
|
||||||
@@ -59,14 +57,12 @@ class OpenPGPPlugin(GajimPlugin):
|
|||||||
'signed-in': (ged.PRECORE, self.signed_in),
|
'signed-in': (ged.PRECORE, self.signed_in),
|
||||||
}
|
}
|
||||||
|
|
||||||
self.modules = [pgp_keylist,
|
self.modules = [openpgp]
|
||||||
openpgp]
|
|
||||||
|
|
||||||
self.encryption_name = ENCRYPTION_NAME
|
self.encryption_name = ENCRYPTION_NAME
|
||||||
self.config_dialog = None
|
self.config_dialog = None
|
||||||
self.gui_extension_points = {
|
self.gui_extension_points = {
|
||||||
'encrypt' + self.encryption_name: (self._encrypt_message, None),
|
'encrypt' + self.encryption_name: (self._encrypt_message, None),
|
||||||
'decrypt': (self._decrypt_message, None),
|
|
||||||
'send_message' + self.encryption_name: (
|
'send_message' + self.encryption_name: (
|
||||||
self._before_sendmessage, None),
|
self._before_sendmessage, None),
|
||||||
'encryption_dialog' + self.encryption_name: (
|
'encryption_dialog' + self.encryption_name: (
|
||||||
@@ -114,8 +110,8 @@ class OpenPGPPlugin(GajimPlugin):
|
|||||||
if con.get_module('OpenPGP').secret_key_available:
|
if con.get_module('OpenPGP').secret_key_available:
|
||||||
log.info('%s => Publish keylist and public key after sign in',
|
log.info('%s => Publish keylist and public key after sign in',
|
||||||
account)
|
account)
|
||||||
con.get_module('OpenPGP').query_key_list()
|
con.get_module('OpenPGP').request_keylist()
|
||||||
con.get_module('OpenPGP').publish_key()
|
con.get_module('OpenPGP').set_public_key()
|
||||||
|
|
||||||
def activate(self):
|
def activate(self):
|
||||||
for account in app.connections:
|
for account in app.connections:
|
||||||
@@ -128,16 +124,17 @@ class OpenPGPPlugin(GajimPlugin):
|
|||||||
if con.get_module('OpenPGP').secret_key_available:
|
if con.get_module('OpenPGP').secret_key_available:
|
||||||
log.info('%s => Publish keylist and public key '
|
log.info('%s => Publish keylist and public key '
|
||||||
'after plugin activation', account)
|
'after plugin activation', account)
|
||||||
con.get_module('OpenPGP').query_key_list()
|
con.get_module('OpenPGP').request_keylist()
|
||||||
con.get_module('OpenPGP').publish_key()
|
con.get_module('OpenPGP').set_public_key()
|
||||||
|
|
||||||
def deactivate(self):
|
def deactivate(self):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _update_caps(account):
|
def _update_caps(account):
|
||||||
if NS_NOTIFY not in app.gajim_optional_features[account]:
|
namespace = nbxmpp.NS_OPENPGP_PK + '+notify'
|
||||||
app.gajim_optional_features[account].append(NS_NOTIFY)
|
if namespace not in app.gajim_optional_features[account]:
|
||||||
|
app.gajim_optional_features[account].append(namespace)
|
||||||
|
|
||||||
def activate_encryption(self, chat_control):
|
def activate_encryption(self, chat_control):
|
||||||
account = chat_control.account
|
account = chat_control.account
|
||||||
@@ -147,21 +144,24 @@ class OpenPGPPlugin(GajimPlugin):
|
|||||||
keys = app.connections[account].get_module('OpenPGP').get_keys(
|
keys = app.connections[account].get_module('OpenPGP').get_keys(
|
||||||
jid, only_trusted=False)
|
jid, only_trusted=False)
|
||||||
if not keys:
|
if not keys:
|
||||||
con.get_module('OpenPGP').query_key_list(jid)
|
con.get_module('OpenPGP').request_keylist(JID(jid))
|
||||||
ErrorDialog(
|
ErrorDialog(
|
||||||
_('No OpenPGP key'),
|
_('No OpenPGP key'),
|
||||||
_('We didnt receive a OpenPGP key from this contact.'))
|
_('We didnt receive a OpenPGP key from this contact.'))
|
||||||
return
|
return
|
||||||
return True
|
return True
|
||||||
else:
|
|
||||||
from openpgp.gtk.wizard import KeyWizard
|
|
||||||
KeyWizard(self, account, chat_control)
|
|
||||||
|
|
||||||
def encryption_state(self, chat_control, state):
|
from openpgp.gtk.wizard import KeyWizard
|
||||||
|
KeyWizard(self, account, chat_control)
|
||||||
|
return False
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def encryption_state(_chat_control, state):
|
||||||
state['authenticated'] = True
|
state['authenticated'] = True
|
||||||
state['visible'] = True
|
state['visible'] = True
|
||||||
|
|
||||||
def on_encryption_button_clicked(self, chat_control):
|
@staticmethod
|
||||||
|
def on_encryption_button_clicked(chat_control):
|
||||||
account = chat_control.account
|
account = chat_control.account
|
||||||
jid = chat_control.contact.jid
|
jid = chat_control.contact.jid
|
||||||
transient = chat_control.parent_win.window
|
transient = chat_control.parent_win.window
|
||||||
@@ -186,12 +186,8 @@ class OpenPGPPlugin(GajimPlugin):
|
|||||||
_('There was no trusted and active key found'))
|
_('There was no trusted and active key found'))
|
||||||
chat_control.sendmessage = False
|
chat_control.sendmessage = False
|
||||||
|
|
||||||
def _encrypt_message(self, con, obj, callback):
|
@staticmethod
|
||||||
|
def _encrypt_message(con, obj, callback):
|
||||||
if not con.get_module('OpenPGP').secret_key_available:
|
if not con.get_module('OpenPGP').secret_key_available:
|
||||||
return
|
return
|
||||||
con.get_module('OpenPGP').encrypt_message(obj, callback)
|
con.get_module('OpenPGP').encrypt_message(obj, callback)
|
||||||
|
|
||||||
def _decrypt_message(self, con, obj, callback):
|
|
||||||
if not con.get_module('OpenPGP').secret_key_available:
|
|
||||||
return
|
|
||||||
con.get_module('OpenPGP').decrypt_message(obj, callback)
|
|
||||||
|
|||||||
Reference in New Issue
Block a user