[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, jid, gnuhome): def __init__(self, key):
self.context = gpg.Context(home_dir=str(gnuhome)) self._key = key
# self.create_new_key() self._uid = self._get_uid()
# self.get_key_by_name()
# self.get_key_by_fingerprint()
self.export_public_key()
def create_new_key(self): def _get_uid(self):
parms = """<GnupgKeyParms format="internal"> for uid in self._key.uids:
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
</GnupgKeyParms>
"""
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))
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:]
def get_keys(self, secret=False):
keys = []
for key in self.context.keylist():
for uid in key.uids:
if uid.uid.startswith('xmpp:'): if uid.uid.startswith('xmpp:'):
keys.append((key, uid.uid[5:])) return uid.uid
break
@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):
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:
if subkey.fpr == key.fpr:
return subkey.fpr, subkey.timestamp
return None, None
def get_keys(self):
keys = []
with gpg.Context(**self._context_args) as context:
for key in context.keylist():
keys.append(KeyringItem(key))
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)