diff --git a/openpgp/backend/pygpg.py b/openpgp/backend/pygpg.py index b7cc838..0c5d663 100644 --- a/openpgp/backend/pygpg.py +++ b/openpgp/backend/pygpg.py @@ -14,8 +14,10 @@ # You should have received a copy of the GNU General Public License # along with OpenPGP Gajim Plugin. If not, see . +import io import os import logging +import tempfile from collections import namedtuple import gnupg @@ -26,6 +28,10 @@ from openpgp.modules.util import DecryptionFailed log = logging.getLogger('gajim.p.openpgp.pygnupg') +gnupg_logger = logging.getLogger('gnupg') +gnupg_logger.addHandler(logging.StreamHandler()) +gnupg_logger.setLevel(logging.WARNING) + KeyringItem = namedtuple('KeyringItem', 'jid keyid fingerprint') @@ -34,11 +40,10 @@ class PGPContext(gnupg.GPG): gnupg.GPG.__init__( self, gpgbinary=app.get_gpg_binary(), gnupghome=str(gnupghome)) - self._passphrase = 'gajimopenpgppassphrase' self._jid = jid.getBare() self._own_fingerprint = None - def _get_key_params(self, jid, passphrase): + def _get_key_params(self, jid): ''' Generate --gen-key input ''' @@ -47,17 +52,17 @@ class PGPContext(gnupg.GPG): 'Key-Type': 'RSA', 'Key-Length': 2048, 'Name-Real': 'xmpp:%s' % jid, - 'Passphrase': passphrase, } out = "Key-Type: %s\n" % params.pop('Key-Type') for key, val in list(params.items()): 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] @@ -119,41 +124,36 @@ class PGPContext(gnupg.GPG): log.error(result.results[0]) return + fingerprint = result.results[0]['fingerprint'] if not self.validate_key(data, str(jid)): - return None - key = self.get_key(result.results[0]['fingerprint']) + self.delete_key(fingerprint) + return + + key = self.get_key(fingerprint) return self._make_keyring_item(key[0]) def validate_key(self, public_key, jid): - import tempfile temppath = os.path.join(tempfile.gettempdir(), 'temp_pubkey') - with open(temppath, 'wb') as tempfile: - tempfile.write(public_key) + with open(temppath, 'wb') as file: + file.write(public_key) result = self.scan_keys(temppath) - if result: - for uid in result.uids: - if uid.startswith('xmpp:'): - if uid[5:] == jid: - key_found = True - else: - log.warning('Found wrong userid in key: %s != %s', - uid[5:], jid) - log.debug(result) - os.remove(temppath) - return False - - if not key_found: - log.warning('No valid userid found in key') - log.debug(result) - os.remove(temppath) - return False - - log.info('Key validation succesful') + if not result: + log.warning('No key found while validating') + log.warning(result) os.remove(temppath) - return True + return False - log.warning('Invalid key data: %s') + for uid in result.uids: + if not uid.startswith('xmpp:'): + continue + + if uid[5:] == jid: + log.info('Key validation succesful') + os.remove(temppath) + return True + + log.warning('No valid userid found in key') log.debug(result) os.remove(temppath) return False @@ -172,10 +172,18 @@ class PGPContext(gnupg.GPG): 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=False) return key + def export_secret_key(self, passphrase): + key = super().export_keys( + self._own_fingerprint, secret=True, armor=False, minimal=False, passphrase='') + + key_file = io.BytesIO(key) + result = super().encrypt_file(key_file, None, armor=False, + symmetric=True, passphrase=passphrase) + return result.data + def delete_key(self, fingerprint): - log.info('Delete Key: %s', fingerprint) - super().delete_keys(fingerprint, passphrase=self._passphrase) + result = super().delete_keys(fingerprint, passphrase='') + log.info('Delete Key: %s, status: %s', fingerprint, result.status) diff --git a/openpgp/gtk/wizard.py b/openpgp/gtk/wizard.py index c1cfff4..6974db7 100644 --- a/openpgp/gtk/wizard.py +++ b/openpgp/gtk/wizard.py @@ -16,6 +16,9 @@ import logging import threading +import string +import random +from textwrap import wrap from enum import IntEnum from gi.repository import Gtk @@ -29,9 +32,11 @@ log = logging.getLogger('gajim.p.openpgp.wizard') class Page(IntEnum): WELCOME = 0 - NEWKEY = 1 - SUCCESS = 2 - ERROR = 3 + FOUND_KEY = 1 + NEWKEY = 2 + SAVE_KEY = 3 + SUCCESS = 4 + ERROR = 5 class KeyWizard(Gtk.Assistant): @@ -44,6 +49,7 @@ class KeyWizard(Gtk.Assistant): self._data_form_widget = None self._is_form = None self._chat_control = chat_control + self.backup_code = None self.set_application(app.app) self.set_transient_for(chat_control.parent_win.window) @@ -54,9 +60,9 @@ class KeyWizard(Gtk.Assistant): self.get_style_context().add_class('dialog-margin') self._add_page(WelcomePage()) - # self._add_page(BackupKeyPage()) + self._add_page(FoundKeyPage()) self._add_page(NewKeyPage(self, self._con)) - # self._add_page(SaveBackupCodePage()) + self._add_page(SaveBackupCodePage()) self._add_page(SuccessfulPage()) self._add_page(ErrorPage()) @@ -73,6 +79,10 @@ class KeyWizard(Gtk.Assistant): self.set_page_title(page, page.title) self.set_page_complete(page, page.complete) + def set_backup_code(self, backup_code): + save_key_page = self.get_nth_page(Page.SAVE_KEY) + save_key_page.set_backup_code(backup_code) + def _remove_sidebar(self): main_box = self.get_children()[0] sidebar = main_box.get_children()[0] @@ -133,22 +143,22 @@ class RequestPage(Gtk.Box): spinner.start() -# class BackupKeyPage(Gtk.Box): +class FoundKeyPage(Gtk.Box): -# type_ = Gtk.AssistantPageType.INTRO -# title = _('Supply Backup Code') -# complete = True + type_ = Gtk.AssistantPageType.INTRO + title = _('Supply Backup Code') + complete = True -# def __init__(self): -# super().__init__(orientation=Gtk.Orientation.VERTICAL) -# self.set_spacing(18) -# title_label = Gtk.Label(label=_('Backup Code')) -# text_label = Gtk.Label( -# label=_('We found a backup Code, please supply your password')) -# self.add(title_label) -# self.add(text_label) -# entry = Gtk.Entry() -# self.add(entry) + def __init__(self): + super().__init__(orientation=Gtk.Orientation.VERTICAL) + self.set_spacing(18) + title_label = Gtk.Label(label=_('Backup Code')) + text_label = Gtk.Label( + label=_('We found a backup Code, please supply your password')) + self.add(title_label) + self.add(text_label) + entry = Gtk.Entry() + self.add(entry) class NewKeyPage(RequestPage): @@ -173,34 +183,62 @@ class NewKeyPage(RequestPage): 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) + @staticmethod + def generate_backup_code(): + range_ = '123456789ABCDEFGHIJKLMNPQRSTUVWXYZ' + code = ''.join(random.choice(range_) for x in range(24)) + return '-'.join(wrap(code.upper(), 4)) + def finished(self, error): - if error is None: - self._assistant.set_current_page(Page.SUCCESS) - else: + if error is not None: log.error(error) self._assistant.set_current_page(Page.ERROR) + return + + self._con.get_module('OpenPGP').get_own_key_details() + if not self._con.get_module('OpenPGP').secret_key_available: + log.error('PGP Error') + self._assistant.set_current_page(Page.ERROR) + return + + backup_code = self.generate_backup_code() + self._assistant.set_backup_code(backup_code) + self._con.get_module('OpenPGP').set_public_key() + self._con.get_module('OpenPGP').request_keylist() + self._con.get_module('OpenPGP').set_secret_key(backup_code) + self._assistant.set_current_page(Page.SAVE_KEY) -# class SaveBackupCodePage(RequestPage): +class SaveBackupCodePage(Gtk.Box): -# type_ = Gtk.AssistantPageType.PROGRESS -# title = _('Save this code') -# complete = False + type_ = Gtk.AssistantPageType.SUMMARY + title = _('Save this code') + complete = True -# def __init__(self): -# super().__init__(orientation=Gtk.Orientation.VERTICAL) -# self.set_spacing(18) -# title_label = Gtk.Label(label=_('Backup Code')) -# text_label = Gtk.Label( -# label=_('This is your backup code, you need it if you reinstall Gajim')) -# self.add(title_label) -# self.add(text_label) + def __init__(self): + super().__init__(orientation=Gtk.Orientation.VERTICAL) + self.set_spacing(18) + title_label = Gtk.Label(label=_('Backup Code')) + text_label = Gtk.Label( + label=_('This is your backup code, you need it if you reinstall Gajim')) + self._code_label = Gtk.Label() + self._code_label.set_selectable(True) + + icon = Gtk.Image.new_from_icon_name('object-select-symbolic', + Gtk.IconSize.DIALOG) + icon.get_style_context().add_class('success-color') + icon.set_valign(Gtk.Align.END) + + self.add(icon) + self.add(title_label) + self.add(text_label) + self.add(self._code_label) + + def set_backup_code(self, backup_code): + self._code_label.set_label(backup_code) class SuccessfulPage(Gtk.Box): diff --git a/openpgp/modules/openpgp.py b/openpgp/modules/openpgp.py index ee1b094..00cb840 100644 --- a/openpgp/modules/openpgp.py +++ b/openpgp/modules/openpgp.py @@ -44,6 +44,7 @@ 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 +from openpgp.backend.aes import aes_encrypt log = logging.getLogger('gajim.p.openpgp') @@ -83,7 +84,7 @@ class OpenPGP(BaseModule): 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._storage = Storage(path) @@ -108,6 +109,11 @@ class OpenPGP(BaseModule): self._nbxmpp('OpenPGP').set_public_key( key, self._fingerprint, self._date) + def set_secret_key(self, passphrase): + log.info('%s => Publish secret key', self._account) + secret_key = self._pgp.export_secret_key(passphrase) + self._nbxmpp('OpenPGP').set_secret_key(secret_key) + def request_public_key(self, jid, fingerprint): log.info('%s => Request public key %s - %s', self._account, fingerprint, jid)