openpgp new

This commit is contained in:
Philipp Hörist
2019-03-31 19:55:53 +02:00
parent 33223f7a53
commit e53150c94e
3 changed files with 125 additions and 73 deletions

View File

@@ -14,8 +14,10 @@
# 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 OpenPGP Gajim Plugin. If not, see <http://www.gnu.org/licenses/>. # along with OpenPGP Gajim Plugin. If not, see <http://www.gnu.org/licenses/>.
import io
import os import os
import logging import logging
import tempfile
from collections import namedtuple from collections import namedtuple
import gnupg import gnupg
@@ -26,6 +28,10 @@ from openpgp.modules.util import DecryptionFailed
log = logging.getLogger('gajim.p.openpgp.pygnupg') 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') KeyringItem = namedtuple('KeyringItem', 'jid keyid fingerprint')
@@ -34,11 +40,10 @@ class PGPContext(gnupg.GPG):
gnupg.GPG.__init__( gnupg.GPG.__init__(
self, gpgbinary=app.get_gpg_binary(), gnupghome=str(gnupghome)) self, gpgbinary=app.get_gpg_binary(), gnupghome=str(gnupghome))
self._passphrase = 'gajimopenpgppassphrase'
self._jid = jid.getBare() 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):
''' '''
Generate --gen-key input Generate --gen-key input
''' '''
@@ -47,17 +52,17 @@ class PGPContext(gnupg.GPG):
'Key-Type': 'RSA', 'Key-Type': 'RSA',
'Key-Length': 2048, 'Key-Length': 2048,
'Name-Real': 'xmpp:%s' % jid, '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()): for key, val in list(params.items()):
out += "%s: %s\n" % (key, val) out += "%s: %s\n" % (key, val)
out += "%no-protection\n"
out += "%commit\n" out += "%commit\n"
return out return out
def generate_key(self): 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): def encrypt(self, payload, keys):
recipients = [key.fingerprint for key in keys] recipients = [key.fingerprint for key in keys]
@@ -119,41 +124,36 @@ class PGPContext(gnupg.GPG):
log.error(result.results[0]) log.error(result.results[0])
return return
fingerprint = result.results[0]['fingerprint']
if not self.validate_key(data, str(jid)): if not self.validate_key(data, str(jid)):
return None self.delete_key(fingerprint)
key = self.get_key(result.results[0]['fingerprint']) return
key = self.get_key(fingerprint)
return self._make_keyring_item(key[0]) return self._make_keyring_item(key[0])
def validate_key(self, public_key, jid): def validate_key(self, public_key, jid):
import tempfile
temppath = os.path.join(tempfile.gettempdir(), 'temp_pubkey') temppath = os.path.join(tempfile.gettempdir(), 'temp_pubkey')
with open(temppath, 'wb') as tempfile: with open(temppath, 'wb') as file:
tempfile.write(public_key) file.write(public_key)
result = self.scan_keys(temppath) result = self.scan_keys(temppath)
if result: if not result:
for uid in result.uids: log.warning('No key found while validating')
if uid.startswith('xmpp:'): log.warning(result)
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')
os.remove(temppath) 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) log.debug(result)
os.remove(temppath) os.remove(temppath)
return False return False
@@ -172,10 +172,18 @@ class PGPContext(gnupg.GPG):
def export_key(self, fingerprint): def export_key(self, fingerprint):
key = super().export_keys( key = super().export_keys(
fingerprint, secret=False, armor=False, minimal=False, fingerprint, secret=False, armor=False, minimal=False)
passphrase=self._passphrase)
return key 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): def delete_key(self, fingerprint):
log.info('Delete Key: %s', fingerprint) result = super().delete_keys(fingerprint, passphrase='')
super().delete_keys(fingerprint, passphrase=self._passphrase) log.info('Delete Key: %s, status: %s', fingerprint, result.status)

View File

@@ -16,6 +16,9 @@
import logging import logging
import threading import threading
import string
import random
from textwrap import wrap
from enum import IntEnum from enum import IntEnum
from gi.repository import Gtk from gi.repository import Gtk
@@ -29,9 +32,11 @@ log = logging.getLogger('gajim.p.openpgp.wizard')
class Page(IntEnum): class Page(IntEnum):
WELCOME = 0 WELCOME = 0
NEWKEY = 1 FOUND_KEY = 1
SUCCESS = 2 NEWKEY = 2
ERROR = 3 SAVE_KEY = 3
SUCCESS = 4
ERROR = 5
class KeyWizard(Gtk.Assistant): class KeyWizard(Gtk.Assistant):
@@ -44,6 +49,7 @@ class KeyWizard(Gtk.Assistant):
self._data_form_widget = None self._data_form_widget = None
self._is_form = None self._is_form = None
self._chat_control = chat_control self._chat_control = chat_control
self.backup_code = None
self.set_application(app.app) self.set_application(app.app)
self.set_transient_for(chat_control.parent_win.window) 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.get_style_context().add_class('dialog-margin')
self._add_page(WelcomePage()) self._add_page(WelcomePage())
# self._add_page(BackupKeyPage()) self._add_page(FoundKeyPage())
self._add_page(NewKeyPage(self, self._con)) self._add_page(NewKeyPage(self, self._con))
# self._add_page(SaveBackupCodePage()) self._add_page(SaveBackupCodePage())
self._add_page(SuccessfulPage()) self._add_page(SuccessfulPage())
self._add_page(ErrorPage()) self._add_page(ErrorPage())
@@ -73,6 +79,10 @@ class KeyWizard(Gtk.Assistant):
self.set_page_title(page, page.title) self.set_page_title(page, page.title)
self.set_page_complete(page, page.complete) 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): def _remove_sidebar(self):
main_box = self.get_children()[0] main_box = self.get_children()[0]
sidebar = main_box.get_children()[0] sidebar = main_box.get_children()[0]
@@ -133,22 +143,22 @@ class RequestPage(Gtk.Box):
spinner.start() spinner.start()
# class BackupKeyPage(Gtk.Box): class FoundKeyPage(Gtk.Box):
# type_ = Gtk.AssistantPageType.INTRO type_ = Gtk.AssistantPageType.INTRO
# title = _('Supply Backup Code') title = _('Supply Backup Code')
# complete = True complete = True
# def __init__(self): def __init__(self):
# super().__init__(orientation=Gtk.Orientation.VERTICAL) super().__init__(orientation=Gtk.Orientation.VERTICAL)
# self.set_spacing(18) self.set_spacing(18)
# title_label = Gtk.Label(label=_('Backup Code')) title_label = Gtk.Label(label=_('Backup Code'))
# text_label = Gtk.Label( text_label = Gtk.Label(
# label=_('We found a backup Code, please supply your password')) label=_('We found a backup Code, please supply your password'))
# self.add(title_label) self.add(title_label)
# self.add(text_label) self.add(text_label)
# entry = Gtk.Entry() entry = Gtk.Entry()
# self.add(entry) self.add(entry)
class NewKeyPage(RequestPage): class NewKeyPage(RequestPage):
@@ -173,34 +183,62 @@ class NewKeyPage(RequestPage):
self._con.get_module('OpenPGP').generate_key() self._con.get_module('OpenPGP').generate_key()
except Exception as e: except Exception as e:
error = 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) 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): def finished(self, error):
if error is None: if error is not None:
self._assistant.set_current_page(Page.SUCCESS)
else:
log.error(error) log.error(error)
self._assistant.set_current_page(Page.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 type_ = Gtk.AssistantPageType.SUMMARY
# title = _('Save this code') title = _('Save this code')
# complete = False complete = True
# def __init__(self): def __init__(self):
# super().__init__(orientation=Gtk.Orientation.VERTICAL) super().__init__(orientation=Gtk.Orientation.VERTICAL)
# self.set_spacing(18) self.set_spacing(18)
# title_label = Gtk.Label(label=_('Backup Code')) title_label = Gtk.Label(label=_('Backup Code'))
# text_label = Gtk.Label( text_label = Gtk.Label(
# label=_('This is your backup code, you need it if you reinstall Gajim')) label=_('This is your backup code, you need it if you reinstall Gajim'))
# self.add(title_label) self._code_label = Gtk.Label()
# self.add(text_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): class SuccessfulPage(Gtk.Box):

View File

@@ -44,6 +44,7 @@ from openpgp.modules.util import prepare_stanza
from openpgp.modules.key_store import PGPContacts 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
from openpgp.backend.aes import aes_encrypt
log = logging.getLogger('gajim.p.openpgp') log = logging.getLogger('gajim.p.openpgp')
@@ -83,7 +84,7 @@ class OpenPGP(BaseModule):
own_bare_jid = self.own_jid.getBare() own_bare_jid = self.own_jid.getBare()
path = Path(configpaths.get('MY_DATA')) / 'openpgp' / own_bare_jid path = Path(configpaths.get('MY_DATA')) / 'openpgp' / own_bare_jid
if not path.exists(): if not path.exists():
path.mkdir(parents=True) path.mkdir(mode=0o700, parents=True)
self._pgp = PGPContext(self.own_jid, path) self._pgp = PGPContext(self.own_jid, path)
self._storage = Storage(path) self._storage = Storage(path)
@@ -108,6 +109,11 @@ class OpenPGP(BaseModule):
self._nbxmpp('OpenPGP').set_public_key( self._nbxmpp('OpenPGP').set_public_key(
key, self._fingerprint, self._date) 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): def request_public_key(self, jid, fingerprint):
log.info('%s => Request public key %s - %s', log.info('%s => Request public key %s - %s',
self._account, fingerprint, jid) self._account, fingerprint, jid)