[openpgp] Add GPGME backend

This commit is contained in:
lovetox
2020-11-21 22:56:55 +01:00
parent a2622aa6c6
commit d8bf566db2
4 changed files with 171 additions and 175 deletions

View File

@@ -15,180 +15,169 @@
# 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
from collections import namedtuple
import logging import logging
from nbxmpp.protocol import JID
import gpg import gpg
from gpg.results import ImportResult
from gajim.common import app from openpgp.modules.util import DecryptionFailed
KeyringItem = namedtuple('KeyringItem', log = logging.getLogger('gajim.p.openpgp.gpgme')
'type keyid userid fingerprint')
log = logging.getLogger('gajim.p.openpgp.pgpme')
class PGPContext(): 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
@property
def fingerprint(self):
return self._key.fpr
@property
def uid(self):
if self._uid is not None:
return self._uid
@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 GPGME:
def __init__(self, jid, gnuhome): def __init__(self, jid, gnuhome):
self.context = gpg.Context(home_dir=str(gnuhome)) self._jid = jid
# self.create_new_key() self._context_args = {
# self.get_key_by_name() 'home_dir': str(gnuhome),
# self.get_key_by_fingerprint() 'offline': True,
self.export_public_key() 'armor': False,
}
def create_new_key(self): def generate_key(self):
parms = """<GnupgKeyParms format="internal"> with gpg.Context(**self._context_args) as context:
Key-Type: RSA result = context.create_key(f'xmpp:{str(self._jid)}',
Key-Length: 2048 expires=False,
Subkey-Type: RSA sign=True,
Subkey-Length: 2048 encrypt=True,
Name-Real: Joe Tester certify=False,
Name-Comment: with stupid passphrase authenticate=False,
Name-Email: test@example.org passphrase=None,
Passphrase: Crypt0R0cks force=False)
Expire-Date: 2020-12-31
</GnupgKeyParms>
"""
with self.context as c: log.info('Generated new key: %s', result.fpr)
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): def get_key(self, fingerprint):
c = gpg.Context() with gpg.Context(**self._context_args) as context:
for key in c.keylist(): try:
user = key.uids[0] key = context.get_key(fingerprint)
print("Keys for %s (%s):" % (user.name, user.email)) 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: for subkey in key.subkeys:
features = [] if subkey.fpr == key.fpr:
if subkey.can_authenticate: return subkey.fpr, subkey.timestamp
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): return None, None
c = gpg.Context()
for key in c.keylist('john'):
print(key.subkeys[0].fpr)
def get_key_by_fingerprint(self): def get_keys(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:]
def get_keys(self, secret=False):
keys = [] keys = []
for key in self.context.keylist(): with gpg.Context(**self._context_args) as context:
for uid in key.uids: for key in context.keylist():
if uid.uid.startswith('xmpp:'): keys.append(KeyringItem(key))
keys.append((key, uid.uid[5:]))
break
return keys return keys
def export_public_key(self): def export_key(self, fingerprint):
# print(dir(self.context)) with gpg.Context(**self._context_args) as context:
result = self.context.key_export_minimal() key = context.key_export_minimal(pattern=fingerprint)
print(result) return key
def encrypt_decrypt_files(self): # def encrypt_decrypt_files(self):
c = gpg.Context() # c = gpg.Context()
recipient = c.get_key("fingerprint of recipient's key") # recipient = c.get_key("fingerprint of recipient's key")
# Encrypt # # Encrypt
with open('foo.txt', 'r') as input_file: # with open('foo.txt', 'r') as input_file:
with open('foo.txt.gpg', 'wb') as output_file: # with open('foo.txt.gpg', 'wb') as output_file:
c.encrypt([recipient], 0, input_file, output_file) # c.encrypt([recipient], 0, input_file, output_file)
# Decrypt # # Decrypt
with open('foo.txt.gpg', 'rb') as input_file: # with open('foo.txt.gpg', 'rb') as input_file:
with open('foo2.txt', 'w') as output_file: # with open('foo2.txt', 'w') as output_file:
c.decrypt(input_file, output_file) # c.decrypt(input_file, output_file)
def encrypt(self): def encrypt(self, plaintext, keys):
c = gpg.Context() recipients = []
recipient = c.get_key("fingerprint of recipient's key") 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)
plaintext_string = u'plain text data' if not recipients:
plaintext_bytes = io.BytesIO(plaintext_string.encode('utf8')) return None, 'No keys found to encrypt to'
encrypted_bytes = io.BytesIO()
c.encrypt([recipient], 0, plaintext_bytes, encrypted_bytes)
def decrypt(self): with gpg.Context(**self._context_args) as context:
c = gpg.Context() result = context.encrypt(str(plaintext).encode(),
decrypted_bytes = io.BytesIO() recipients,
c.decrypt(encrypted_bytes, decrypted_bytes) always_trust=True)
decrypted_string = decrypted_bytes.getvalue().decode('utf8')
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)

View File

@@ -31,7 +31,7 @@ if log.getEffectiveLevel() == logging.DEBUG:
KeyringItem = namedtuple('KeyringItem', 'jid keyid fingerprint') KeyringItem = namedtuple('KeyringItem', 'jid keyid fingerprint')
class PGPContext(gnupg.GPG): class PythonGnuPG(gnupg.GPG):
def __init__(self, jid, gnupghome): def __init__(self, jid, gnupghome):
gnupg.GPG.__init__( gnupg.GPG.__init__(
self, gpgbinary='gpg', gnupghome=str(gnupghome)) self, gpgbinary='gpg', gnupghome=str(gnupghome))

View File

@@ -93,12 +93,6 @@ class KeyWizard(Gtk.Assistant):
elif self.get_current_page() == Page.SUCCESS: elif self.get_current_page() == Page.SUCCESS:
self._activate_encryption() 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): def _on_cancel(self, widget):
self.destroy() self.destroy()
@@ -168,22 +162,23 @@ class NewKeyPage(RequestPage):
thread.start() thread.start()
def worker(self): def worker(self):
error = None text = None
try: try:
self._con.get_module('OpenPGP').generate_key() self._con.get_module('OpenPGP').generate_key()
except Exception as e: except Exception as error:
error = e text = str(error)
else:
self._con.get_module('OpenPGP').get_own_key_details() GLib.idle_add(self.finished, text)
self._con.get_module('OpenPGP').set_public_key()
self._con.get_module('OpenPGP').request_keylist()
GLib.idle_add(self.finished, error)
def finished(self, error): def finished(self, error):
if error is None: if error is None:
self._assistant.set_current_page(Page.SUCCESS) 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: 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) self._assistant.set_current_page(Page.ERROR)
@@ -229,7 +224,7 @@ class SuccessfulPage(Gtk.Box):
class ErrorPage(Gtk.Box): class ErrorPage(Gtk.Box):
type_ = Gtk.AssistantPageType.SUMMARY type_ = Gtk.AssistantPageType.SUMMARY
title = _('Registration failed') title = _('Setup failed')
complete = True complete = True
def __init__(self): def __init__(self):

View File

@@ -14,6 +14,7 @@
# 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 sys
import time import time
import logging import logging
from pathlib import Path from pathlib import Path
@@ -44,7 +45,11 @@ from openpgp.modules.util import DecryptionFailed
from openpgp.modules.util import prepare_stanza 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
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') log = logging.getLogger('gajim.p.openpgp')
@@ -84,9 +89,9 @@ 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 = PGPBackend(self.own_jid, path)
self._storage = Storage(path) self._storage = Storage(path)
self._contacts = PGPContacts(self._pgp, self._storage) self._contacts = PGPContacts(self._pgp, self._storage)
self._fingerprint, self._date = self.get_own_key_details() self._fingerprint, self._date = self.get_own_key_details()
@@ -223,7 +228,14 @@ class OpenPGP(BaseModule):
if not any(map(self.own_jid.bareMatch, recipients)): if not any(map(self.own_jid.bareMatch, recipients)):
log.warning('to attr not valid') log.warning('to attr not valid')
log.warning(payload) log.warning(str(payload))
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 return
log.info('Received OpenPGP message from: %s', properties.jid) log.info('Received OpenPGP message from: %s', properties.jid)