diff -Nru gajim-openpgp-1.2.15/backend/gpgme.py gajim-openpgp-1.3.9/backend/gpgme.py --- gajim-openpgp-1.2.15/backend/gpgme.py 2020-07-08 22:22:49.000000000 +0000 +++ gajim-openpgp-1.3.9/backend/gpgme.py 2020-12-27 23:59:23.000000000 +0000 @@ -15,180 +15,169 @@ # along with OpenPGP Gajim Plugin. If not, see . -import io -from collections import namedtuple import logging +from nbxmpp.protocol import JID + import gpg +from gpg.results import ImportResult + +from openpgp.modules.util import DecryptionFailed + +log = logging.getLogger('gajim.p.openpgp.gpgme') + + +class KeyringItem: + def __init__(self, key): + self._key = key + self._uid = self._get_uid() + + def _get_uid(self): + for uid in self._key.uids: + if uid.uid.startswith('xmpp:'): + return uid.uid -from gajim.common import app + @property + def fingerprint(self): + return self._key.fpr -KeyringItem = namedtuple('KeyringItem', - 'type keyid userid fingerprint') + @property + def uid(self): + if self._uid is not None: + return self._uid -log = logging.getLogger('gajim.p.openpgp.pgpme') + @property + def jid(self): + if self._uid is not None: + return JID.from_string(self._uid[5:]) + def __hash__(self): + return hash(self.fingerprint) -class PGPContext(): + + +class GPGME: def __init__(self, jid, gnuhome): - self.context = gpg.Context(home_dir=str(gnuhome)) - # self.create_new_key() - # self.get_key_by_name() - # self.get_key_by_fingerprint() - self.export_public_key() - - def create_new_key(self): - parms = """ - Key-Type: RSA - Key-Length: 2048 - Subkey-Type: RSA - Subkey-Length: 2048 - Name-Real: Joe Tester - Name-Comment: with stupid passphrase - Name-Email: test@example.org - Passphrase: Crypt0R0cks - Expire-Date: 2020-12-31 - - """ - - with self.context as c: - c.set_engine_info(gpg.constants.PROTOCOL_OpenPGP, None, app.gajimpaths['MY_DATA']) - c.set_progress_cb(gpg.callbacks.progress_stdout) - c.op_genkey(parms, None, None) - print("Generated key with fingerprint {0}.".format( - c.op_genkey_result().fpr)) - - def get_all_keys(self): - c = gpg.Context() - for key in c.keylist(): - user = key.uids[0] - print("Keys for %s (%s):" % (user.name, user.email)) + self._jid = jid + self._context_args = { + 'home_dir': str(gnuhome), + 'offline': True, + 'armor': False, + } + + def generate_key(self): + with gpg.Context(**self._context_args) as context: + result = context.create_key(f'xmpp:{str(self._jid)}', + expires=False, + sign=True, + encrypt=True, + certify=False, + authenticate=False, + passphrase=None, + force=False) + + log.info('Generated new key: %s', result.fpr) + + def get_key(self, fingerprint): + with gpg.Context(**self._context_args) as context: + try: + key = context.get_key(fingerprint) + except gpg.errors.KeyNotFound as error: + log.warning('key not found: %s', error.keystr) + return + + except Exception as error: + log.warning('get_key() error: %s', error) + return + + return key + + def get_own_key_details(self): + with gpg.Context(**self._context_args) as context: + keys = list(context.keylist(secret=True)) + if not keys: + return None, None + + key = keys[0] for subkey in key.subkeys: - features = [] - if subkey.can_authenticate: - features.append('auth') - if subkey.can_certify: - features.append('cert') - if subkey.can_encrypt: - features.append('encrypt') - if subkey.can_sign: - features.append('sign') - print(' %s %s' % (subkey.fpr, ','.join(features))) - - def get_key_by_name(self): - c = gpg.Context() - for key in c.keylist('john'): - print(key.subkeys[0].fpr) - - def get_key_by_fingerprint(self): - c = gpg.Context() - fingerprint = 'key fingerprint to search for' - try: - key = c.get_key(fingerprint) - print('%s (%s)' % (key.uids[0].name, key.uids[0].email)) - except gpg.errors.KeyNotFound: - print("No key for fingerprint '%s'." % fingerprint) - - def get_secret_key(self): - ''' - Key(can_authenticate=1, - can_certify=1, - can_encrypt=1, - can_sign=1, - chain_id=None, - disabled=0, - expired=0, - fpr='7ECE1F88BAFCA37F168E1556A4DBDD1BA55FE3CE', - invalid=0, - is_qualified=0, - issuer_name=None, - issuer_serial=None, - keylist_mode=1, - last_update=0, - origin=0, - owner_trust=5, - protocol=0, - revoked=0, - secret=1, - subkeys=[ - SubKey(can_authenticate=1, - can_certify=1, - can_encrypt=1, - can_sign=1, - card_number=None - curve=None, - disabled=0, - expired=0, - expires=0, - fpr='7ECE1F88BAFCA37F168E1556A4DBDD1BA55FE3CE', - invalid=0, - is_cardkey=0, - is_de_vs=1, - is_qualified=0, - keygrip='15BECB77301E4810ABB9CA6A9391158E575DABEC', - keyid='A4DBDD1BA55FE3CE', - length=2048, - pubkey_algo=1, - revoked=0, - secret=1, - timestamp=1525006759)], - uids=[ - UID(address=None, - comment='', - email='', - invalid=0, - last_update=0, - name='xmpp:philw@jabber.at', - origin=0, - revoked=0, - signatures=[], - tofu=[], - uid='xmpp:philw@jabber.at', - validity=5)]) - ''' - for key in self.context.keylist(secret=True): - break - return key.fpr, key.fpr[-16:] + if subkey.fpr == key.fpr: + return subkey.fpr, subkey.timestamp - def get_keys(self, secret=False): + return None, None + + def get_keys(self): keys = [] - for key in self.context.keylist(): - for uid in key.uids: - if uid.uid.startswith('xmpp:'): - keys.append((key, uid.uid[5:])) - break + with gpg.Context(**self._context_args) as context: + for key in context.keylist(): + keys.append(KeyringItem(key)) return keys - def export_public_key(self): - # print(dir(self.context)) - result = self.context.key_export_minimal() - print(result) - - def encrypt_decrypt_files(self): - c = gpg.Context() - recipient = c.get_key("fingerprint of recipient's key") - - # Encrypt - with open('foo.txt', 'r') as input_file: - with open('foo.txt.gpg', 'wb') as output_file: - c.encrypt([recipient], 0, input_file, output_file) - - # Decrypt - with open('foo.txt.gpg', 'rb') as input_file: - with open('foo2.txt', 'w') as output_file: - c.decrypt(input_file, output_file) - - def encrypt(self): - c = gpg.Context() - recipient = c.get_key("fingerprint of recipient's key") - - plaintext_string = u'plain text data' - plaintext_bytes = io.BytesIO(plaintext_string.encode('utf8')) - encrypted_bytes = io.BytesIO() - c.encrypt([recipient], 0, plaintext_bytes, encrypted_bytes) - - def decrypt(self): - c = gpg.Context() - decrypted_bytes = io.BytesIO() - c.decrypt(encrypted_bytes, decrypted_bytes) - decrypted_string = decrypted_bytes.getvalue().decode('utf8') + def export_key(self, fingerprint): + with gpg.Context(**self._context_args) as context: + key = context.key_export_minimal(pattern=fingerprint) + return key + + # def encrypt_decrypt_files(self): + # c = gpg.Context() + # recipient = c.get_key("fingerprint of recipient's key") + + # # Encrypt + # with open('foo.txt', 'r') as input_file: + # with open('foo.txt.gpg', 'wb') as output_file: + # c.encrypt([recipient], 0, input_file, output_file) + + # # Decrypt + # with open('foo.txt.gpg', 'rb') as input_file: + # with open('foo2.txt', 'w') as output_file: + # c.decrypt(input_file, output_file) + + def encrypt(self, plaintext, keys): + recipients = [] + with gpg.Context(**self._context_args) as context: + for key in keys: + key = context.get_key(key.fingerprint) + if key is not None: + recipients.append(key) + + if not recipients: + return None, 'No keys found to encrypt to' + + with gpg.Context(**self._context_args) as context: + result = context.encrypt(str(plaintext).encode(), + recipients, + always_trust=True) + + ciphertext, result, _sign_result = result + return ciphertext, None + + def decrypt(self, ciphertext): + with gpg.Context(**self._context_args) as context: + try: + result = context.decrypt(ciphertext) + except Exception as error: + raise DecryptionFailed('Decryption failed: %s' % error) + + plaintext, result, verify_result = result + plaintext = plaintext.decode() + + fingerprints = [sig.fpr for sig in verify_result.signatures] + if not fingerprints or len(fingerprints) > 1: + log.error(result) + log.error(verify_result) + raise DecryptionFailed('Verification failed') + + return plaintext, fingerprints[0] + + def import_key(self, data, jid): + log.info('Import key from %s', jid) + with gpg.Context(**self._context_args) as context: + result = context.key_import(data) + if not isinstance(result, ImportResult) or result.imported != 1: + log.error('Key import failed: %s', jid) + log.error(result) + return + + fingerprint = result.imports[0].fpr + key = self.get_key(fingerprint) + + return KeyringItem(key) diff -Nru gajim-openpgp-1.2.15/backend/pygpg.py gajim-openpgp-1.3.9/backend/pygpg.py --- gajim-openpgp-1.2.15/backend/pygpg.py 2020-07-08 22:22:49.000000000 +0000 +++ gajim-openpgp-1.3.9/backend/pygpg.py 2020-12-27 23:59:23.000000000 +0000 @@ -31,16 +31,15 @@ KeyringItem = namedtuple('KeyringItem', 'jid keyid fingerprint') -class PGPContext(gnupg.GPG): +class PythonGnuPG(gnupg.GPG): def __init__(self, jid, gnupghome): - gnupg.GPG.__init__( - self, gpgbinary='gpg', gnupghome=str(gnupghome)) + gnupg.GPG.__init__(self, gpgbinary='gpg', gnupghome=str(gnupghome)) - self._passphrase = 'gajimopenpgppassphrase' self._jid = jid.getBare() self._own_fingerprint = None - def _get_key_params(self, jid, passphrase): + @staticmethod + def _get_key_params(jid): ''' Generate --gen-key input ''' @@ -49,17 +48,17 @@ 'Key-Type': 'RSA', 'Key-Length': 2048, 'Name-Real': 'xmpp:%s' % jid, - 'Passphrase': passphrase, } - out = "Key-Type: %s\n" % params.pop('Key-Type') + out = 'Key-Type: %s\n' % params.pop('Key-Type') for key, val in list(params.items()): - out += "%s: %s\n" % (key, val) - out += "%commit\n" + out += '%s: %s\n' % (key, val) + out += '%no-protection\n' + out += '%commit\n' return out def generate_key(self): - super().gen_key(self._get_key_params(self._jid, self._passphrase)) + super().gen_key(self._get_key_params(self._jid)) def encrypt(self, payload, keys): recipients = [key.fingerprint for key in keys] @@ -71,8 +70,7 @@ recipients, armor=False, sign=self._own_fingerprint, - always_trust=True, - passphrase=self._passphrase) + always_trust=True) if result.ok: error = '' @@ -82,9 +80,7 @@ return result.data, error def decrypt(self, payload): - result = super().decrypt(payload, - always_trust=True, - passphrase=self._passphrase) + result = super().decrypt(payload, always_trust=True) if not result.ok: raise DecryptionFailed(result.status) @@ -118,7 +114,7 @@ result = super().import_keys(data) if not result: log.error('Could not import key') - log.error(result.results[0]) + log.error(result) return if not self.validate_key(data, str(jid)): @@ -134,6 +130,7 @@ result = self.scan_keys(temppath) if result: + key_found = False for uid in result.uids: if uid.startswith('xmpp:'): if uid[5:] == jid: @@ -174,10 +171,9 @@ def export_key(self, fingerprint): key = super().export_keys( - fingerprint, secret=False, armor=False, minimal=False, - passphrase=self._passphrase) + fingerprint, secret=False, armor=False, minimal=True) return key def delete_key(self, fingerprint): log.info('Delete Key: %s', fingerprint) - super().delete_keys(fingerprint, passphrase=self._passphrase) + super().delete_keys(fingerprint) diff -Nru gajim-openpgp-1.2.15/debian/changelog gajim-openpgp-1.3.9/debian/changelog --- gajim-openpgp-1.2.15/debian/changelog 2020-07-08 22:25:20.000000000 +0000 +++ gajim-openpgp-1.3.9/debian/changelog 2020-12-31 16:42:46.000000000 +0000 @@ -1,3 +1,15 @@ +gajim-openpgp (1.3.9-2) unstable; urgency=medium + + * Re-upload to unstable + + -- Martin Thu, 31 Dec 2020 16:42:46 +0000 + +gajim-openpgp (1.3.9-1) experimental; urgency=medium + + * New upstream version, compatible with Gajim master + + -- Martin Mon, 28 Dec 2020 00:04:19 +0000 + gajim-openpgp (1.2.15-1) unstable; urgency=medium * New upstream version diff -Nru gajim-openpgp-1.2.15/debian/control gajim-openpgp-1.3.9/debian/control --- gajim-openpgp-1.2.15/debian/control 2020-07-08 22:25:11.000000000 +0000 +++ gajim-openpgp-1.3.9/debian/control 2020-12-31 16:42:42.000000000 +0000 @@ -15,8 +15,8 @@ Package: gajim-openpgp Architecture: all Depends: ${misc:Depends}, - gajim (>= 1.2.0~), - python3-gnupg (>= 0.4.1), + gajim (>= 1.2.91~), + python3-gpg, python3-pkg-resources Description: Gajim plugin for OpenPGP encryption This Plugin adds support for OpenPGP encryption to Gajim diff -Nru gajim-openpgp-1.2.15/gtk/key.py gajim-openpgp-1.3.9/gtk/key.py --- gajim-openpgp-1.2.15/gtk/key.py 2020-07-08 22:22:49.000000000 +0000 +++ gajim-openpgp-1.3.9/gtk/key.py 2020-12-27 23:59:23.000000000 +0000 @@ -21,8 +21,8 @@ from gajim.common import app -from gajim.gtk.dialogs import NewConfirmationDialog -from gajim.gtk.dialogs import DialogButton +from gajim.gui.dialogs import ConfirmationDialog +from gajim.gui.dialogs import DialogButton from gajim.plugins.plugins_i18n import _ from openpgp.modules.util import Trust @@ -38,10 +38,10 @@ 'warning-color'), Trust.BLIND: ('security-medium-symbolic', _('Blind Trust'), - 'openpgp-dark-success-color'), + 'encrypted-color'), Trust.VERIFIED: ('security-high-symbolic', _('Verified'), - 'success-color') + 'encrypted-color') } @@ -120,7 +120,7 @@ self.key.delete() self.destroy() - NewConfirmationDialog( + ConfirmationDialog( _('Delete'), _('Delete Public Key'), _('This will permanently delete this public key'), @@ -231,7 +231,7 @@ type_ = Trust.VERIFIED icon = 'security-high-symbolic' label = _('Verified') - color = 'success-color' + color = 'encrypted-color' def __init__(self): MenuOption.__init__(self) diff -Nru gajim-openpgp-1.2.15/gtk/style.css gajim-openpgp-1.3.9/gtk/style.css --- gajim-openpgp-1.2.15/gtk/style.css 2020-07-08 22:22:49.000000000 +0000 +++ gajim-openpgp-1.3.9/gtk/style.css 2020-12-27 23:59:22.000000000 +0000 @@ -1,4 +1,3 @@ -.openpgp-dark-success-color { color: darker(@success_color); } .openpgp-inactive-color { color: @unfocused_borders; } .openpgp-mono { font-size: 12px; font-family: monospace; } diff -Nru gajim-openpgp-1.2.15/gtk/wizard.py gajim-openpgp-1.3.9/gtk/wizard.py --- gajim-openpgp-1.2.15/gtk/wizard.py 2020-07-08 22:22:49.000000000 +0000 +++ gajim-openpgp-1.3.9/gtk/wizard.py 2020-12-27 23:59:23.000000000 +0000 @@ -93,12 +93,6 @@ elif self.get_current_page() == Page.SUCCESS: self._activate_encryption() - def _on_error(self, error_text): - log.info('Show Error page') - page = self.get_nth_page(Page.ERROR) - page.set_text(error_text) - self.set_current_page(Page.ERROR) - def _on_cancel(self, widget): self.destroy() @@ -168,22 +162,23 @@ thread.start() def worker(self): - error = None + text = None try: self._con.get_module('OpenPGP').generate_key() - except Exception as e: - error = e - else: - self._con.get_module('OpenPGP').get_own_key_details() - self._con.get_module('OpenPGP').set_public_key() - self._con.get_module('OpenPGP').request_keylist() - GLib.idle_add(self.finished, error) + except Exception as error: + text = str(error) + + GLib.idle_add(self.finished, text) def finished(self, error): if error is None: self._assistant.set_current_page(Page.SUCCESS) + self._con.get_module('OpenPGP').get_own_key_details() + self._con.get_module('OpenPGP').set_public_key() + self._con.get_module('OpenPGP').request_keylist() else: - log.error(error) + error_page = self._assistant.get_nth_page(Page.ERROR) + error_page.set_text(error) self._assistant.set_current_page(Page.ERROR) @@ -229,7 +224,7 @@ class ErrorPage(Gtk.Box): type_ = Gtk.AssistantPageType.SUMMARY - title = _('Registration failed') + title = _('Setup failed') complete = True def __init__(self): diff -Nru gajim-openpgp-1.2.15/manifest.ini gajim-openpgp-1.3.9/manifest.ini --- gajim-openpgp-1.2.15/manifest.ini 2020-07-08 22:22:49.000000000 +0000 +++ gajim-openpgp-1.3.9/manifest.ini 2020-12-27 23:59:23.000000000 +0000 @@ -1,9 +1,9 @@ [info] name: OpenPGP short_name: openpgp -version: 1.2.15 +version: 1.3.9 description: Experimental OpenPGP (XEP-0373) implementation. authors: Philipp Hörist homepage: https://dev.gajim.org/gajim/gajim-plugins/wikis/pgp -min_gajim_version: 1.1.91 -max_gajim_version: 1.2.90 +min_gajim_version: 1.2.91 +max_gajim_version: 1.3.90 diff -Nru gajim-openpgp-1.2.15/modules/openpgp.py gajim-openpgp-1.3.9/modules/openpgp.py --- gajim-openpgp-1.2.15/modules/openpgp.py 2020-07-08 22:22:49.000000000 +0000 +++ gajim-openpgp-1.3.9/modules/openpgp.py 2020-12-27 23:59:23.000000000 +0000 @@ -14,6 +14,7 @@ # You should have received a copy of the GNU General Public License # along with OpenPGP Gajim Plugin. If not, see . +import sys import time import logging from pathlib import Path @@ -21,8 +22,9 @@ from nbxmpp.namespaces import Namespace from nbxmpp import Node from nbxmpp import StanzaMalformed -from nbxmpp.util import is_error_result from nbxmpp.structs import StanzaHandler +from nbxmpp.errors import StanzaError +from nbxmpp.errors import MalformedStanzaError from nbxmpp.modules.openpgp import PGPKeyMetadata from nbxmpp.modules.openpgp import parse_signcrypt from nbxmpp.modules.openpgp import create_signcrypt_node @@ -43,7 +45,11 @@ from openpgp.modules.util import prepare_stanza from openpgp.modules.key_store import PGPContacts from openpgp.backend.sql import Storage -from openpgp.backend.pygpg import PGPContext + +if sys.platform == 'win32': + from openpgp.backend.pygpg import PythonGnuPG as PGPBackend +else: + from openpgp.backend.gpgme import GPGME as PGPBackend log = logging.getLogger('gajim.p.openpgp') @@ -83,9 +89,9 @@ own_bare_jid = self.own_jid.getBare() path = Path(configpaths.get('MY_DATA')) / 'openpgp' / own_bare_jid if not path.exists(): - path.mkdir(parents=True) + path.mkdir(mode=0o700, parents=True) - self._pgp = PGPContext(self.own_jid, path) + self._pgp = PGPBackend(self.own_jid, path) self._storage = Storage(path) self._contacts = PGPContacts(self._pgp, self._storage) self._fingerprint, self._date = self.get_own_key_details() @@ -117,10 +123,13 @@ callback=self._public_key_received, user_data=fingerprint) - def _public_key_received(self, result, fingerprint): - if is_error_result(result): + def _public_key_received(self, task): + fingerprint = task.get_user_data() + try: + result = task.finish() + except (StanzaError, MalformedStanzaError) as error: log.error('%s => Public Key not found: %s', - self._account, result) + self._account, error) return imported_key = self._pgp.import_key(result.key, result.jid) @@ -152,16 +161,19 @@ callback=self._keylist_received, user_data=jid) - def _keylist_received(self, result, jid): - if is_error_result(result): + def _keylist_received(self, task): + jid = task.get_user_data() + try: + keylist = task.finish() + except (StanzaError, MalformedStanzaError) as error: log.error('%s => Keylist query failed: %s', - self._account, result) + self._account, error) if self.own_jid.bareMatch(jid) and self._fingerprint is not None: self.set_keylist() return log.info('Keylist received from %s', jid) - self._process_keylist(result, jid) + self._process_keylist(keylist, jid) def _process_keylist(self, keylist, from_jid): if not keylist: @@ -208,15 +220,22 @@ signcrypt = Node(node=payload) try: - payload, to, timestamp = parse_signcrypt(signcrypt) + payload, recipients, _timestamp = parse_signcrypt(signcrypt) except StanzaMalformed as error: log.warning('Decryption failed: %s', error) log.warning(payload) return - if not self.own_jid.bareMatch(to): + if not any(map(self.own_jid.bareMatch, recipients)): log.warning('to attr not valid') - log.warning(payload) + log.warning(signcrypt) + return + + keys = self._contacts.get_keys(properties.jid.bare) + fingerprints = [key.fingerprint for key in keys] + if fingerprint not in fingerprints: + log.warning('Invalid fingerprint on message: %s', fingerprint) + log.warning('Expected: %s', fingerprints) return log.info('Received OpenPGP message from: %s', properties.jid) @@ -234,7 +253,9 @@ keys += self._contacts.get_keys(self.own_jid) keys += [Key(self._fingerprint, None)] - payload = create_signcrypt_node(obj.stanza, NOT_ENCRYPTED_TAGS) + payload = create_signcrypt_node(obj.stanza, + [obj.jid], + NOT_ENCRYPTED_TAGS) encrypted_payload, error = self._pgp.encrypt(payload, keys) if error: diff -Nru gajim-openpgp-1.2.15/pgpplugin.py gajim-openpgp-1.3.9/pgpplugin.py --- gajim-openpgp-1.2.15/pgpplugin.py 2020-07-08 22:22:49.000000000 +0000 +++ gajim-openpgp-1.3.9/pgpplugin.py 2020-12-27 23:59:23.000000000 +0000 @@ -29,7 +29,7 @@ from gajim.common import helpers from gajim.common.const import CSSPriority -from gajim.gtk.dialogs import ErrorDialog +from gajim.gui.dialogs import ErrorDialog from gajim.plugins import GajimPlugin from gajim.plugins.plugins_i18n import _