diff --git a/Makefile b/Makefile index 7ab1bad..e135b0d 100644 --- a/Makefile +++ b/Makefile @@ -1,3 +1,3 @@ clean: - rm -f *.pyc + find . -name "*.pyc" -type f -delete diff --git a/RELEASE_NOTES b/RELEASE_NOTES index e98f505..a9e7478 100644 --- a/RELEASE_NOTES +++ b/RELEASE_NOTES @@ -4,8 +4,12 @@ This is the v0.3 release and bring a few tiny fixes all over the program. Changes ========== +-Major + * New gpg library which replaces monkeysign dependency * Optimised finding the correct key to download * Removed once discovered clients if they disappear + +-Minor * Experimental compatibility for older Gtk versions (set_always_show_image) * Minor UI improvement: Do not delete the manually typed fingerprint when going back * Also accept more correctly typed fingerprint with a more relaxed verification routine @@ -17,5 +21,5 @@ Resources ========= Download: https://github.com/muelli/geysigning/releases/download/0.3/gnome-keysign-0.3.tar.gz -sha256sum: +sha256sum: Web site: https://wiki.gnome.org/GnomeKeysign diff --git a/keysign/GPGQRCode.py b/keysign/GPGQRCode.py index 0a81b45..30e5c3c 100755 --- a/keysign/GPGQRCode.py +++ b/keysign/GPGQRCode.py @@ -1,5 +1,6 @@ #!/usr/bin/env python # Copyright 2014 Tobias Mueller +# Copyright 2015 Andrei Macavei # # This file is part of GNOME Keysign. # @@ -20,20 +21,24 @@ for keys and selects the one matching your input """ from gi.repository import Gtk -from monkeysign.gpg import Keyring +import gpgme +from gpg import gpg from QRCode import QRImage def main(): import sys + if len(sys.argv) < 2: + print ("Usage: {} ".format(sys.argv[0])) + sys.exit(1) key = sys.argv[1] - keyring = Keyring() - keys = keyring.get_keys(key) + keyring = gpgme.Context() + keys = gpg.get_keylist(keyring, key) # Heh, we take the first key here. Maybe we should raise a warning # or so, when there is more than one key. - fpr = keys.items()[0][0] + fpr = keys[0].subkeys[0].fpr data = 'OPENPGP4FPR:' + fpr - + w = Gtk.Window() w.connect("delete-event", Gtk.main_quit) w.set_default_size(100,100) diff --git a/keysign/KeyPresent.py b/keysign/KeyPresent.py index 1a1b3b2..081ff95 100644 --- a/keysign/KeyPresent.py +++ b/keysign/KeyPresent.py @@ -1,6 +1,7 @@ #!/usr/bin/env python # encoding: utf-8 # Copyright 2014 Tobias Mueller +# Copyright 2015 Andrei Macavei # # This file is part of GNOME Keysign. # @@ -26,7 +27,8 @@ from gi.repository import Gtk, GLib from gi.repository import GObject -from monkeysign.gpg import Keyring +import gpgme +from gpg import gpg # These are relative imports from __init__ import __version__ @@ -41,7 +43,7 @@ class KeyPresent(Gtk.Application): """A demo application showing how to display sufficient details about a key such that it can be sent securely. - + Note that the main purpose is to enable secure transfer, not reviewing key details. As such, the implementation might change a lot, depending on the method of secure transfer. @@ -127,16 +129,14 @@ def main(args=sys.argv): format='%(name)s (%(levelname)s): %(message)s') try: arguments = parse_command_line(args) - + #if arguments.gpg: # keyid = arguments.file - # keyring = Keyring() - # # this is a dict {fpr: key-instance} - # found_keys = keyring.get_keys(keyid) + # ctx = gpgme.Context() + # found_keys = gpg.get_keylist(ctx, keyid) # # We take the first item we found and export the actual keydata - # fpr = found_keys.items()[0][0] - # keyring.export_data(fpr=fpr, secret=False) - # keydata = keyring.context.stdout + # fpr = found_keys[0].subkeys[0].fpr + # keydata = gpg.extract_keydata(ctx, fpr, True) #else: # keydata = open(arguments.file, 'r').read() fpr = arguments.fpr @@ -146,12 +146,12 @@ def main(args=sys.argv): GLib.unix_signal_add_full(GLib.PRIORITY_HIGH, signal.SIGINT, lambda *args : app.quit(), None) except AttributeError: pass - + exit_status = app.run(fpr) - + return exit_status - + finally: logging.shutdown() diff --git a/keysign/KeysPage.py b/keysign/KeysPage.py index 2b8c6dd..3a2beb0 100644 --- a/keysign/KeysPage.py +++ b/keysign/KeysPage.py @@ -1,5 +1,6 @@ #!/usr/bin/env python # encoding: utf-8 +# Copyright 2015 Andrei Macavei # Copyright 2014 Tobias Mueller # # This file is part of GNOME Keysign. @@ -26,9 +27,10 @@ from gi.repository import Gtk, GLib from gi.repository import GObject -from monkeysign.gpg import Keyring +import gpgme # These are relative imports +from gpg import gpg from __init__ import __version__ log = logging.getLogger() @@ -37,10 +39,10 @@ class KeysPage(Gtk.VBox): '''This represents a list of keys with the option for the user to select one key to proceed. - + This class emits a `key-selection-changed' signal when the user initially selects a key such that it is highlighted. - + The `key-selected' signal is emitted when the user commits to a key, i.e. by pressing a designated button to make his selection public. @@ -58,7 +60,7 @@ class KeysPage(Gtk.VBox): def __init__(self, show_public_keys=False): '''Sets the widget up. - + The show_public_keys parameter is meant for development purposes only. If set to True, the widget will show the public keys, too. Otherwise, secret keys are shown. @@ -71,19 +73,18 @@ def __init__(self, show_public_keys=False): # TreeView, i.e. in get_items_from_selection. self.store = Gtk.ListStore(str, str, str) - # FIXME: this should be moved to KeySignSection - self.keyring = Keyring() # the user's keyring + # XXX: maybe we can move this to KeySignSection in Sections.py + self.ctx = gpgme.Context() self.keysDict = {} - # FIXME: this should be a callback function to update the display - # when a key is changed/deleted - for key in self.keyring.get_keys(None, secret=True, public=show_public_keys).values(): - if key.invalid or key.disabled or key.expired or key.revoked: + # FIXME: implement a callback that refreshes the UIDs when they change + for key in gpg.get_personal_keys(): + if key.revoked or key.expired or key.invalid or key.disabled or key.subkeys[0].disabled: continue - uidslist = key.uidslist #UIDs: Real Name (Comment) - keyid = str(key.keyid()) # the key's short id + uidslist = key.uids + keyid = key.subkeys[0].fpr[-8:] if not keyid in self.keysDict: self.keysDict[keyid] = key @@ -125,7 +126,7 @@ def __init__(self, show_public_keys=False): self.treeView.append_column(nameColumn) self.treeView.append_column(emailColumn) self.treeView.append_column(keyColumn) - + self.treeView.connect('row-activated', self.on_row_activated) # make the tree view resposive to single click selection @@ -173,14 +174,14 @@ def get_items_from_selection(self, selection=None): def on_selection_changed(self, selection, *args): log.debug('Selected new TreeView item %s = %s', selection, args) - + name, email, keyid = self.get_items_from_selection(selection) - + key = self.keysDict[keyid] self.emit('key-selection-changed', keyid) - + try: - exp_date = datetime.fromtimestamp(float(key.expiry)) + exp_date = datetime.fromtimestamp(float(key.subkeys[0].expires)) expiry = "{:%Y-%m-%d %H:%M:%S}".format(exp_date) except ValueError, e: expiry = "No expiration date" @@ -207,7 +208,7 @@ def on_selection_changed(self, selection, *args): def on_row_activated(self, treeview, tree_path, column): '''A callback for when the user "activated" a row, e.g. by double-clicking an entry. - + It emits the key-selected signal. ''' # We just hijack the existing function. @@ -222,7 +223,7 @@ def on_publish_button_clicked(self, button, key, *args): to publish a key on the network. It will emit a "key-selected" signal with the ID of the selected key.''' log.debug('Clicked publish for key (%s) %s (%s)', type(key), key, args) - keyid = key.keyid() + keyid = key.subkeys[0].fpr[-8:] self.emit('key-selected', keyid) @@ -230,7 +231,7 @@ def on_publish_button_clicked(self, button, key, *args): class Keys(Gtk.Application): """A widget which displays keys in a user's Keyring. - + Once the user has selected a key, the key-selected signal will be thrown. """ @@ -273,7 +274,7 @@ def on_activate(self, app): def on_key_selection_changed(self, button, key): """This is the connected to the KeysPage's key-selection-changed signal - + As a user of that widget, you would show more details in the GUI or prepare for a final commitment by the user. """ @@ -282,13 +283,13 @@ def on_key_selection_changed(self, button, key): def on_key_selected(self, button, key): """This is the connected to the KeysPage's key-selected signal - + As a user of that widget, you would enable buttons or proceed with the GUI. """ self.log.info('User committed to a key! %s', key) - + def parse_command_line(argv): """Parse command line argument. See -h option @@ -321,18 +322,18 @@ def main(args=sys.argv): format='%(name)s (%(levelname)s): %(message)s') try: arguments = parse_command_line(args) - + app = Keys() try: GLib.unix_signal_add_full(GLib.PRIORITY_HIGH, signal.SIGINT, lambda *args : app.quit(), None) except AttributeError: pass - + exit_status = app.run(None) - + return exit_status - + finally: logging.shutdown() diff --git a/keysign/Keyserver.py b/keysign/Keyserver.py index 3892bee..5dde96c 100755 --- a/keysign/Keyserver.py +++ b/keysign/Keyserver.py @@ -105,6 +105,7 @@ def start(self, data=None, fpr=None, port=None, *args, **kwargs): tries = 10 + fpr = self.fpr if self.fpr else "FIXME fingerprint" kd = data if data else self.keydata class KeyRequestHandler(KeyRequestHandlerBase): diff --git a/keysign/Sections.py b/keysign/Sections.py index 457c5a4..5525272 100644 --- a/keysign/Sections.py +++ b/keysign/Sections.py @@ -29,9 +29,8 @@ import sys -from monkeysign.gpg import Keyring, TempKeyring -from monkeysign.ui import MonkeysignUi -from monkeysign.gpg import GpgRuntimeError +from keysign.gpg import gpg +import gpgme from compat import gtkbutton import Keyserver @@ -49,7 +48,7 @@ # Needed for window.get_xid(), xvimagesink.set_window_handle(), respectively: from gi.repository import GstVideo -import key + Gst.init([]) @@ -78,105 +77,6 @@ -# FIXME: This probably wants to go somewhere more central. -# Maybe even into Monkeysign. -log = logging.getLogger() -def UIDExport(uid, keydata): - """Export only the UID of a key. - Unfortunately, GnuPG does not provide smth like - --export-uid-only in order to obtain a UID and its - signatures.""" - tmp = TempKeyring() - # Hm, apparently this needs to be set, otherwise gnupg will issue - # a stray "gpg: checking the trustdb" which confuses the gnupg library - tmp.context.set_option('always-trust') - tmp.import_data(keydata) - for fpr, key in tmp.get_keys(uid).items(): - for u in key.uidslist: - key_uid = u.uid - if key_uid != uid: - log.info('Deleting UID %s from key %s', key_uid, fpr) - tmp.del_uid(fingerprint=fpr, pattern=key_uid) - only_uid = tmp.export_data(uid) - - return only_uid - - -def MinimalExport(keydata): - '''Returns the minimised version of a key - - For now, you must provide one key only.''' - tmpkeyring = TempKeyring() - ret = tmpkeyring.import_data(keydata) - log.debug("Returned %s after importing %s", ret, keydata) - assert ret - tmpkeyring.context.set_option('export-options', 'export-minimal') - keys = tmpkeyring.get_keys() - log.debug("Keys after importing: %s (%s)", keys, keys.items()) - # We assume the keydata to contain one key only - fingerprint, key = keys.items()[0] - stripped_key = tmpkeyring.export_data(fingerprint) - return stripped_key - - - -class TempKeyringCopy(TempKeyring): - """A temporary keyring which uses the secret keys of a parent keyring - - It mainly copies the public keys from the parent keyring to this temporary - keyring and sets this keyring up such that it uses the secret keys of the - parent keyring. - """ - def __init__(self, keyring, *args, **kwargs): - self.keyring = keyring - # Not a new style class... - if issubclass(self.__class__, object): - super(TempKeyringCopy, self).__init__(*args, **kwargs) - else: - TempKeyring.__init__(self, *args, **kwargs) - - self.log = logging.getLogger() - - tmpkeyring = self - # Copy and paste job from monkeysign.ui.prepare - tmpkeyring.context.set_option('secret-keyring', keyring.homedir + '/secring.gpg') - - # copy the gpg.conf from the real keyring - try: - from_ = keyring.homedir + '/gpg.conf' - to_ = tmpkeyring.homedir - shutil.copy(from_, to_) - self.log.debug('copied your gpg.conf from %s to %s', from_, to_) - except IOError as e: - # no such file or directory is alright: it means the use - # has no gpg.conf (because we are certain the temp homedir - # exists at this point) - if e.errno != 2: - pass - - - # Copy the public parts of the secret keys to the tmpkeyring - signing_keys = [] - for fpr, key in keyring.get_keys(None, secret=True, public=False).items(): - if not key.invalid and not key.disabled and not key.expired and not key.revoked: - signing_keys.append(key) - tmpkeyring.import_data (keyring.export_data (fpr)) - - - -## Monkeypatching to get more debug output -import monkeysign.gpg -bc = monkeysign.gpg.Context.build_command -def build_command(*args, **kwargs): - ret = bc(*args, **kwargs) - #log.info("Building command %s", ret) - log.debug("Building cmd: %s", ' '.join(["'%s'" % c for c in ret])) - return ret -monkeysign.gpg.Context.build_command = build_command - - - - class KeySignSection(Gtk.VBox): def __init__(self, app): @@ -191,7 +91,7 @@ def __init__(self, app): self.app = app self.log = logging.getLogger() - self.keyring = Keyring() + self.ctx = gpgme.Context() # these are needed later when we need to get details about # a selected key @@ -243,27 +143,25 @@ def on_key_selected(self, pane, keyid): to a key, i.e. the user has made a selection and wants to advance the program. ''' - log.debug('User selected key %s', keyid) + self.log.info('User selected key %s', keyid) - key = self.keyring.get_keys(keyid).values()[0] + key = self.keysPage.keysDict[keyid] + fpr = key.subkeys[0].fpr - keyid = key.keyid() - fpr = key.fpr - self.keyring.export_data(fpr, secret=False) - keydata = self.keyring.context.stdout + keydata = gpg.export_key(self.ctx, fpr, True) self.log.debug("Keyserver switched on! Serving key with fpr: %s", fpr) self.app.setup_server(keydata, fpr) - - self.switch_to_key_present_page(key) + self.switch_to_key_present_page(fpr) - def switch_to_key_present_page(self, key): + + def switch_to_key_present_page(self, fpr): '''This switches the notebook to the page which presents the information that is needed to securely transfer the keydata, i.e. the fingerprint and its barcode. ''' - self.keyPresentPage.display_fingerprint_qr_page(key) + self.keyPresentPage.display_fingerprint_qr_page(fpr) self.notebook.next_page() # This is more of a crude hack. Once the next page is presented, # the back button has the focus. This is not desirable because @@ -272,17 +170,17 @@ def switch_to_key_present_page(self, key): # FIXME: we better use set_current_page, but that requires # knowing which page our desired widget is on. # FWIW: A headerbar has named pages. - + def on_next_button_clicked(self, button): '''A helper for legacy reasons to enable a next button - + All it does is retrieve the selection from the TreeView and call the signal handler for when the user committed to a key ''' name, email, keyid = self.keysPage.get_items_from_selection() return self.on_key_selected(button, keyid) - + def on_button_clicked(self, button): @@ -313,7 +211,7 @@ def __init__(self, app): self.log = logging.getLogger() # the temporary keyring we operate in - self.tmpkeyring = None + self.ctx = None self.scanPage = ScanFingerprintPage() self.signPage = SignKeyPage() @@ -357,6 +255,8 @@ def __init__(self, app): # A list holding references to temporary files which should probably # be cleaned up on exit... self.tmpfiles = [] + # A path to a tmp gpg homedir + self.tmp_gpghome = None def set_progress_bar(self): page_index = self.notebook.get_current_page() @@ -398,6 +298,9 @@ def on_barcode(self, sender, barcode, message=None): def download_key_http(self, address, port): + '''Downloads a key from a keyserver and provides + bytes (as opposed to an unencoded string). + ''' url = ParseResult( scheme='http', # This seems to work well enough with both IPv6 and IPv4 @@ -406,7 +309,8 @@ def download_key_http(self, address, port): params='', query='', fragment='') - return requests.get(url.geturl()).text + return requests.get(url.geturl()).content + def try_download_keys(self, clients): for client in clients: @@ -422,8 +326,9 @@ def try_download_keys(self, clients): def verify_downloaded_key(self, downloaded_data, fingerprint): # FIXME: implement a better and more secure way to verify the key - if self.tmpkeyring.import_data(downloaded_data): - imported_key_fpr = self.tmpkeyring.get_keys().keys()[0] + res = gpg.import_keydata(self.ctx, downloaded_data) + if res and len(res.imports): + imported_key_fpr = res.imports[0][0] if imported_key_fpr == fingerprint: result = True else: @@ -446,10 +351,11 @@ def obtain_key_async(self, fingerprint, callback=None, data=None, error_cb=None) other_clients = self.app.discovered_services self.log.debug("The clients found on the network: %s", other_clients) - #FIXME: should we create a new TempKeyring for each key we want - # to sign it ? - self.tmpkeyring = TempKeyring() - + # For each key downloaded we create a new gpgme.Context object and + # set up a temporary dir for gpg + self.ctx = gpgme.Context() + self.tmp_gpghome = gpg.set_engine(self.ctx, protocol=gpgme.PROTOCOL_OpenPGP, dir_prefix='tmp.gpghome') + other_clients = self.sort_clients(other_clients, fingerprint) for keydata in self.try_download_keys(other_clients): @@ -486,52 +392,48 @@ def obtain_key_async(self, fingerprint, callback=None, data=None, error_cb=None) def sign_key_async(self, fingerprint, callback=None, data=None, error_cb=None): self.log.debug("I will sign key with fpr {}".format(fingerprint)) - keyring = Keyring() - keyring.context.set_option('export-options', 'export-minimal') - - tmpkeyring = TempKeyringCopy(keyring) + ctx = gpgme.Context() + gpg_homedir = gpg.set_engine(ctx) - # 1. fetch the key into a temporary keyring - # 1.a) from the local keyring - # FIXME: WTF?! How would the ring enter the keyring in first place?! keydata = data or self.received_key_data - - if keydata: - stripped_key = MinimalExport(keydata) - else: # Do we need this branch at all? + # FIXME: until this (https://code.launchpad.net/~daniele-athome/pygpgme/pygpgme/+merge/173333) + # gets merged in trunk, we cannot export the key with 'export-minimal' option + # stripped_key = gpg.gpg_export(ctx, keydata, True, gpgme.EXPORT_MODE_MINIMAL) + if not keydata: self.log.debug("looking for key %s in your keyring", fingerprint) - keyring.context.set_option('export-options', 'export-minimal') - stripped_key = keyring.export_data(fingerprint) - - self.log.debug('Trying to import key\n%s', stripped_key) - if tmpkeyring.import_data(stripped_key): - # 3. for every user id (or all, if -a is specified) - # 3.1. sign the uid, using gpg-agent - keys = tmpkeyring.get_keys(fingerprint) + default_ctx = gpgme.Context() + keydata = gpg.export_key(default_ctx, fingerprint, True) + + # 1. Fetch the key into a temporary keyring + self.log.debug('Trying to import key\n%s', keydata) + if gpg.import_keydata(ctx, keydata): + + keys = gpg.get_keylist(ctx, fingerprint) self.log.info("Found keys %s for fp %s", keys, fingerprint) assert len(keys) == 1, "We received multiple keys for fp %s: %s" % (fingerprint, keys) - key = keys[fingerprint] - uidlist = key.uidslist - - # FIXME: For now, we sign all UIDs. This is bad. - ret = tmpkeyring.sign_key(uidlist[0].uid, signall=True) - self.log.info("Result of signing %s on key %s: %s", uidlist[0].uid, fingerprint, ret) + key = keys[0] + uidlist = key.uids + # 2. Sign each UID individually for uid in uidlist: uid_str = uid.uid self.log.info("Processing uid %s %s", uid, uid_str) - # 3.2. export and encrypt the signature - # 3.3. mail the key to the user - signed_key = UIDExport(uid_str, tmpkeyring.export_data(uid_str)) + res = gpg.sign_uid(ctx, gpg_homedir, uid) + if not res: + # we may have already signed this uid before + self.log.info("Uid %s was signed before.\nUpdating signature made by key: %s", + uid_str, key.subkeys[0].fpr) + + # 3. Export and encrypt the signature + signed_key = gpg.export_key(ctx, fingerprint, True) self.log.info("Exported %d bytes of signed key", len(signed_key)) - # self.signui.tmpkeyring.context.set_option('armor') - tmpkeyring.context.set_option('always-trust') - encrypted_key = tmpkeyring.encrypt_data(data=signed_key, recipient=uid_str) - keyid = str(key.keyid()) - ctx = { + encrypted_key = gpg.encrypt_data(ctx, signed_key, uid_str) + + keyid = key.subkeys[0].fpr[-8:] + template_ctx = { 'uid' : uid_str, 'fingerprint': fingerprint, 'keyid': keyid, @@ -553,25 +455,51 @@ def sign_key_async(self, fingerprint, callback=None, data=None, error_cb=None): # As we're done with the file, we close it. #tmpfile.close() - subject = Template(SUBJECT).safe_substitute(ctx) - body = Template(BODY).safe_substitute(ctx) + # mail the key to the user + subject = Template(SUBJECT).safe_substitute(template_ctx) + body = Template(BODY).safe_substitute(template_ctx) self.email_file (to=uid_str, subject=subject, body=body, files=[filename]) - # FIXME: Can we get rid of self.tmpfiles here already? Even if the MUA is still running? + # FIXME: Find a better and cleaner way to do this ... + # This is a sort of ugly hack for us to sign each UID individually and send an email + # with only that UID signed. The reason is that we cannot delete a signature from a key + # that is imported in the temp gpg context. So , to bypass this, we do: + # 1. Sign a UID for the key + # 2. Send an email with the key having only that UID signed + # 3. Delete and re-import the key from the local context + # 4. Re-fetch the key from the local keylist + # 5. Repeat + try: + ctx.delete(key) + except gpgme.GpgmeError as exp: + self.log.debug('You are signing one of your own keys: %s', key.subkeys[0].fpr) + ctx.delete(key, True) + + gpg.import_keydata(ctx, keydata) + keys = gpg.get_keylist(ctx, fingerprint) + self.log.info("Found keys %s for fp %s", keys, fingerprint) + assert len(keys) == 1, "We received multiple keys for fp %s: %s" % (fingerprint, keys) + + # Because we have to re-import the key after each UID sign, we must update the reference + key = keys[0] + # FIXME: Can we get rid of self.tmpfiles here already? Even if the MUA is still running? # 3.4. optionnally (-l), create a local signature and import in # local keyring # 4. trash the temporary keyring - else: self.log.error('data found in barcode does not match a OpenPGP fingerprint pattern: %s', fingerprint) if error_cb: GLib.idle_add(error_cb, data) + # We are done signing the key so we remove the temporary keyring + self.log.info("Deleting temporary gpg home dir: %s", gpg_homedir) + gpg.reset_engine(ctx, gpg_homedir) + return False @@ -656,69 +584,12 @@ def on_button_clicked(self, button, *args, **kwargs): def recieved_key(self, fingerprint, keydata, *data): self.received_key_data = keydata - openpgpkey = self.tmpkeyring.get_keys(fingerprint).values()[0] - self.signPage.display_downloaded_key(openpgpkey, fingerprint) - - - - -class SignUi(MonkeysignUi): - """sign a key in a safe fashion. - -This program assumes you have gpg-agent configured to prompt for -passwords.""" - - def __init__(self, app, args = None): - super(SignUi, self).__init__(args) - - self.app = app - - - def main(self): - - MonkeysignUi.main(self) - - def yes_no(self, prompt, default = None): - # dialog = Gtk.MessageDialog(self.app.window, 0, Gtk.MessageType.INFO, - # Gtk.ButtonsType.YES_NO, prompt) - # response = dialog.run() - # dialog.destroy() - - # return response == Gtk.ResponseType.YES - # Simply return True for now - return True - - def choose_uid(self, prompt, key): - # dialog = Gtk.Dialog(prompt, self.app.window, 0, - # (Gtk.STOCK_CANCEL, Gtk.ResponseType.REJECT, - # Gtk.STOCK_OK, Gtk.ResponseType.ACCEPT)) - - # label = Gtk.Label(prompt) - # dialog.vbox.pack_start(label, False, False, 0) - # label.show() - - # self.uid_radios = None - # for uid in key.uidslist: - # r = Gtk.RadioButton.new_with_label_from_widget( - # self.uid_radios, uid.uid) - # r.show() - # dialog.vbox.pack_start(r, False, False, 0) - - # if self.uid_radios is None: - # self.uid_radios = r - # self.uid_radios.set_active(True) - # else: - # self.uid_radios.set_active(False) - - # response = dialog.run() - - # label = None - # if response == Gtk.ResponseType.ACCEPT: - # self.app.log.info("okay signing") - # label = [ r for r in self.uid_radios.get_group() if r.get_active()][0].get_label() - # else: - # self.app.log.info('user denied signature') - - # dialog.destroy() - # return label - return None + keylist = gpg.get_keylist(self.ctx, fingerprint, False) + self.log.debug('Getting keylist: %r', keylist) + # Delete temporary dir after we're done getting the key + if self.tmp_gpghome: + gpg.reset_engine(self.ctx, self.tmp_gpghome) + self.log.info("Deleting tmp gpg homedir: %s", self.tmp_gpghome) + + gpgmeKey = keylist[0] + self.signPage.display_downloaded_key(gpg.format_key(gpgmeKey)) diff --git a/keysign/SignPages.py b/keysign/SignPages.py index 757dcaa..71ed454 100644 --- a/keysign/SignPages.py +++ b/keysign/SignPages.py @@ -23,9 +23,11 @@ import StringIO from gi.repository import GObject, Gtk, GLib, GdkPixbuf -from monkeysign.gpg import Keyring from qrencode import encode_scaled +import gpgme +from gpg import gpg + from datetime import datetime from compat import gtkbutton @@ -36,45 +38,29 @@ log = logging.getLogger() -def parse_sig_list(text): - '''Parses GnuPG's signature list (i.e. list-sigs) - - The format is described in the GnuPG man page''' - sigslist = [] - for block in text.split("\n"): - if block.startswith("sig"): - record = block.split(":") - log.debug("sig record (%d) %s", len(record), record) - keyid, timestamp, uid = record[4], record[5], record[9] - sigslist.append((keyid, timestamp, uid)) - - return sigslist - # This is a cache for a keyring object, so that we do not need # to create a new object every single time we parse signatures -_keyring = None -def signatures_for_keyid(keyid, keyring=None): +_context = None +def signatures_for_keyid(keyid, context=None): '''Returns the list of signatures for a given key id - - This will call out to GnuPG list-sigs, using Monkeysign, - and parse the resulting string into a list of signatures. - + + This calls gpg.get_sig_list which returns a list with all + sigs for this uid + A default Keyring will be used unless you pass an instance as keyring argument. ''' # Retrieving a cached instance of a keyring, # unless we were being passed a keyring - global _keyring - if keyring is not None: - kr = keyring + global _context + if context is not None: + ctx = context else: - if _keyring is None: - _keyring = Keyring() - kr = _keyring + _context = _context if _context else gpgme.Context() + ctx = _context - # FIXME: this would be better if it was done in monkeysign - kr.context.call_command(['list-sigs', keyid]) - siglist = parse_sig_list(kr.context.stdout) + sigs = gpg.get_siglist(ctx, keyid) + siglist = [(sig.keyid, sig.timestamp, sig.uid) for sig in sigs] return siglist @@ -113,15 +99,15 @@ def __init__(self, fpr=None): self.pack_start(leftVBox, True, True, 0) self.pack_start(self.rightVBox, True, True, 0) - + if self.fpr: self.setup_fingerprint_widget(self.fpr) - def display_fingerprint_qr_page(self, openPgpKey=None): - assert openPgpKey or self.fpr + def display_fingerprint_qr_page(self, fpr): + assert fpr or self.fpr - rawfpr = openPgpKey.fpr if openPgpKey else self.fpr + rawfpr = fpr if fpr else self.fpr self.fpr = rawfpr self.setup_fingerprint_widget(self.fpr) @@ -160,9 +146,6 @@ def __init__(self): self.set_spacing(10) self.log = logging.getLogger() - # FIXME: this should be moved to KeySignSection - self.keyring = Keyring() - uidsLabel = Gtk.Label() uidsLabel.set_text("UIDs") @@ -185,7 +168,7 @@ def __init__(self): self.pack_start(self.signaturesBox, True, True, 0) - def display_uids_signatures_page(self, openPgpKey): + def display_uids_signatures_page(self, gpgmeKey): # destroy previous uids for uid in self.uidsBox.get_children(): @@ -195,7 +178,7 @@ def display_uids_signatures_page(self, openPgpKey): # display a list of uids labels = [] - for uid in openPgpKey.uidslist: + for uid in gpgmeKey.uids: label = Gtk.Label(str(uid.uid)) label.set_line_wrap(True) labels.append(label) @@ -205,16 +188,16 @@ def display_uids_signatures_page(self, openPgpKey): label.show() try: - exp_date = datetime.fromtimestamp(float(openPgpKey.expiry)) + exp_date = datetime.fromtimestamp(float(gpgmeKey.subkeys[0].expires)) expiry = "Expires {:%Y-%m-%d %H:%M:%S}".format(exp_date) except ValueError, e: expiry = "No expiration date" self.expireLabel.set_markup(expiry) - - + + ### Set up signatures - keyid = str(openPgpKey.keyid()) + keyid = str(gpgmeKey.subkeys[0].fpr[-8:]) sigslist = signatures_for_keyid(keyid) SHOW_SIGNATURES = False @@ -230,10 +213,10 @@ def display_uids_signatures_page(self, openPgpKey): date = datetime.fromtimestamp(float(timestamp)) sigLabel.set_markup(str(keyid) + "\t\t" + date.ctime()) sigLabel.set_line_wrap(True) - + self.signaturesBox.pack_start(sigLabel, False, False, 0) sigLabel.show() - + sigLabel = Gtk.Label() sigLabel.set_markup("%d signatures" % len(sigslist)) sigLabel.set_line_wrap(True) @@ -299,7 +282,7 @@ def get_text_from_textview(self): start_iter = self.textbuffer.get_start_iter() end_iter = self.textbuffer.get_end_iter() raw_text = self.textbuffer.get_text(start_iter, end_iter, False) - + return raw_text @@ -319,11 +302,11 @@ def __init__(self): self.pack_start(self.mainLabel, False, False, 0) - def display_downloaded_key(self, key, scanned_fpr): + def display_downloaded_key(self, formatted_key): # FIXME: If the two fingerprints don't match, the button # should be disabled - key_text = GLib.markup_escape_text(str(key)) + key_text = GLib.markup_escape_text(str(formatted_key)) markup = """\ diff --git a/keysign/__init__.py b/keysign/__init__.py index b4af0f8..380e068 100644 --- a/keysign/__init__.py +++ b/keysign/__init__.py @@ -1,5 +1,6 @@ #!/usr/bin/env python # Copyright 2014 Tobias Mueller +# Copyright 2015 Andrei Macavei # # This file is part of GNOME Keysign. # @@ -24,9 +25,9 @@ def main(): # can be imported without wanting to run it, e.g. setup.py # imports the __version__ import logging, sys, signal - + from gi.repository import GLib, Gtk - + from .MainWindow import MainWindow app = MainWindow() diff --git a/keysign/gpg/__init__.py b/keysign/gpg/__init__.py new file mode 100644 index 0000000..fef66b5 --- /dev/null +++ b/keysign/gpg/__init__.py @@ -0,0 +1 @@ +#!/usr/bin/env python \ No newline at end of file diff --git a/keysign/gpg/gpg.py b/keysign/gpg/gpg.py new file mode 100644 index 0000000..b722ef7 --- /dev/null +++ b/keysign/gpg/gpg.py @@ -0,0 +1,315 @@ +#!/usr/bin/env python +# Copyright 2015 Andrei Macavei +# +# This file is part of GNOME Keysign. +# +# GNOME Keysign is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# GNOME Keysign is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with GNOME Keysign. If not, see . + + +import logging +from string import Template + +import os +import sys +import shutil +import tempfile +import subprocess + +import gpgme +import gpgme.editutil +from io import BytesIO +from datetime import datetime +try: + from StringIO import StringIO +except ImportError: + from io import StringIO + + +log = logging.getLogger(__name__) + +def set_engine(gpgmeContext, protocol=gpgme.PROTOCOL_OpenPGP, dir_prefix=None): + """Sets up a temporary directory as new gnupg home + for this context + """ + dir_prefix = dir_prefix if dir_prefix else 'tmp.gpghome' + temp_dir = tempfile.mkdtemp(prefix=dir_prefix) + gpgmeContext.set_engine_info(protocol, None, temp_dir) + return temp_dir + + +def reset_engine(gpgmeContext, tmp_dir=None, protocol=gpgme.PROTOCOL_OpenPGP): + """Resets the gnupg homedir for the received context + """ + gpgmeContext.set_engine_info(protocol, None, None) + if tmp_dir: + shutil.rmtree(tmp_dir, ignore_errors=True) + + +def export_secret_keys(): + """Exports the user's private keys. + + This will export the private keys found in the current GnuPG homedir. + """ + + try: + gnupg_home = os.environ['GNUPGHOME'] + except KeyError as err: + gnupg_home = os.environ['HOME'] + '/.gnupg' + + # At least a private key must be inside the current GnuPG homedir + assert len([key for key in gpgme.Context().keylist(None,True)]) != 0, \ + ("Error: no private key found inside gpg homedir: %s" % (gnupg_home,)) + + keydata = subprocess.check_output(["gpg", "--armor", "--export-secret-keys"]) + assert len(keydata) != 0, ("Error: 0-bytes privatekey data exported") + + log.debug("Exported private key data:\n%s",keydata) + + return keydata + + +def import_private_key(gpgmeContext, secret_key=None): + """Imports the user's private key from @secret_key or the default keyring + to a temporary context. + """ + if secret_key: + keydata = secret_key + else: + # Until the author of pygpgme (James Henstridge) doesn't merge this: + # https://code.launchpad.net/~daniele-athome/pygpgme/pygpgme/+merge/173333 + # , we cannot use the new GPGME features such as private key export or minimal export. + # We use this hack to export the private keys to a file from which we'll import in our keyring. + keydata = export_secret_keys() + + with BytesIO(keydata) as fp: + gpgmeContext.import_(fp) + + # Import the personal public keys also + ctx = gpgme.Context() + keys = [key for key in ctx.keylist(None, True)] + + for key in keys: + if not key.revoked and not key.expired and not key.invalid and not key.subkeys[0].disabled: + import_key(gpgmeContext, key.subkeys[0].fpr) + log.debug("imported your personal key: %s to tmp keyring", key.subkeys[0].fpr) + + + +def import_key(gpgmeContext, fpr): + """Imports a key from the user's keyring into the keyring received + as argument. + + It assumes that the received keyring (@gpgmeContext) has its gpg homedir set already. + """ + # Get the default keyring + ctx = gpgme.Context() + keydata = BytesIO() + ctx.export(fpr, keydata) + + if not keydata.getvalue(): + log.error("No key found in user's keyring with fpr:\n%s", fpr) + raise ValueError('Invalid fingerprint') + + keydata.seek(0) + res = gpgmeContext.import_(keydata) + return len(res.imports) != 0 + + +def import_keydata(gpgmeContext, keydata): + """Tries to import a OpenPGP key from @keydata. + Keydata needs to be bytes (or an encoded string). + + The @gpgmeContext object has a gpg directory already set. + """ + keydataIO = BytesIO(keydata) + result = None + try: + result = gpgmeContext.import_(keydataIO) + except gpgme.GpgmeError as err: + log.error("Couldn't import the key with the following keydata:\n%s", keydataIO.getvalue()) + return result + + +def get_keylist(gpgmeContext, keyid=None, secret=False, pair=False): + """Returns the keys found in @gpgmeContext + + @keyid: the unique id of a key. + @secret: if set to True it returns the secret keys, else it returns the public keys. + @pair: if True it returns only the public keys that are associated with a secret key + """ + secret_keys = [key for key in gpgmeContext.keylist(keyid, True)] + if secret: + return secret_keys + + public_keys = [key for key in gpgmeContext.keylist(keyid, False)] + if pair: + pair_keys = [] + for key in public_keys: + for skey in secret_keys: + if key.subkeys[0].fpr == skey.subkeys[0].fpr: + pair_keys.append(key) + break + return pair_keys + + return public_keys + +def get_personal_keys(keyid=None, secret=False): + """Returns the personal keys found in User's own keyring. + + @keyid: the unique id of a key. + @secret: if set to True it returns the secret part of the keys, + else it returns the public part. + """ + ctx = gpgme.Context() # the user's personal keyring + secret_personal_keys = [key for key in ctx.keylist(keyid, True)] + if secret: + return secret_personal_keys + + all_public_keys = [key for key in ctx.keylist(None, False)] + + public_personal_keys = [] + for key in all_public_keys: + for skey in secret_personal_keys: + if key.subkeys[0].fpr == skey.subkeys[0].fpr: + public_personal_keys.append(key) + break + + return public_personal_keys + + +def get_siglist(gpgmeContext, keyid): + '''Returns a list with all signatures for this @keyid + ''' + siglist = set() + gpgmeContext.keylist_mode = gpgme.KEYLIST_MODE_SIGS + key = gpgmeContext.get_key(keyid) + + for uid in key.uids: + sigs = [sig for sig in uid.signatures] + siglist = siglist.union(sigs) + + return list(siglist) + + +def sign_uid(gpgmeContext, gpg_homedir, userId, secret_key=None): + """Signs a specific uid of a OpenPGP key + + gpgmeContext: the temporary keyring + gpg_homedir: the current GPG directory + userId: sign this userId + secret_key: if this is given than it will be used as the primary key + + @rtype: bool + @return: True/False depending if this uid was signed for the first time + by this key + """ + import_private_key(gpgmeContext, secret_key) + + primary_key = [key for key in gpgmeContext.keylist(None, True)][0] + gpgmeContext.signers = [primary_key] + + try: + uid_name, uid_email, uid_comment = userId.name, userId.email, userId.comment + except AttributeError as exp: + msg = "Invalid UserId object: %r" % userId + log.error(msg) + raise ValueError(msg) + + # We set keylist mode so we can see signatures + gpgmeContext.keylist_mode = gpgme.KEYLIST_MODE_SIGS + key = gpgmeContext.get_key(userId.uid) + + first_sig = True + + for (i, uid) in enumerate(key.uids): + # Check if this uid is the same with the one that we want to sign + if uid.name == uid_name and uid.email == uid_email and uid.comment == uid_comment: + + sigs = [sig for sig in uid.signatures if primary_key.subkeys[0].fpr.endswith(sig.keyid)] + if not len(sigs) == 0: + # This uid was signed by this key in the past. Update signature but set 'first_sig' to False + first_sig = False + + gpgme.editutil.edit_sign(gpgmeContext, key, index=i, check=0) + break + + return first_sig + + +def encrypt_data(gpgmeContext, data, uid, armor=True): + """Encrypts @data for the recipients @uid + """ + plaintext = BytesIO(data) + ciphertext = BytesIO() + gpgmeContext.armor = armor + recipients = get_keylist(gpgmeContext, uid) + + gpgmeContext.encrypt(recipients, gpgme.ENCRYPT_ALWAYS_TRUST, + plaintext, ciphertext) + return ciphertext.getvalue() + + +def export_key(gpgmeContext, fpr, armor=False, mode=None): + """Exports the key with the given fingerprint from the user's keyring. + + The key can be exported in ASCII-armored format if armor is set. + """ + gpgmeContext.armor = armor + keydata = BytesIO() + if mode: + gpgmeContext.export(fpr, keydata, mode) + else: + gpgmeContext.export(fpr, keydata) + return keydata.getvalue() + + +def format_fpr(fpr): + """display a clean version of the fingerprint + + this is the display we usually see + """ + l = list(fpr) # explode + s = '' + for i in range(10): + # output 4 chars + s += ''.join(l[4*i:4*i+4]) + # add a space, except at the end + if i < 9: s += ' ' + # add an extra space in the middle + if i == 4: s += ' ' + return s + + +def format_key(gpgmeKey): + """Returns a string representation of @gpgmeKey + + It contains info about: length of key, keyid, expiration date, creation date, + fingerprint, uids and subkeys. + """ + subkey = gpgmeKey.subkeys[0] + + ret = u'pub %sR/%s' % (subkey.length, subkey.fpr[-8:]) + ret += u' %s' % (subkey.timestamp, ) + if subkey.expires: ret += u' [expires: %s]' % (subkey.expires,) + ret += '\n' + ret += u' Fingerprint = %s\n' % (format_fpr(subkey.fpr),) + i = 1 + for uid in gpgmeKey.uids: + ret += u"uid %d %s\n" % (i, uid.uid.decode('utf8')) + i += 1 + for sk in gpgmeKey.subkeys: + if not sk.fpr.startswith(subkey.fpr): + ret += u"sub %sR/%s %s" % (sk.length, sk.fpr[-8:], sk.timestamp) + if sk.expires: ret += u' [expires: %s]\n' % (sk.expires,) + return ret diff --git a/keysign/gpg/keys/signonly.pub b/keysign/gpg/keys/signonly.pub new file mode 100644 index 0000000..9d12dab --- /dev/null +++ b/keysign/gpg/keys/signonly.pub @@ -0,0 +1,32 @@ +-----BEGIN PGP PUBLIC KEY BLOCK----- +Version: GnuPG v1 + +mI0EVbqCqAEEAKYNDbu4zCTOtOb8tnODaA7WnBHM8rSFFR4Hyd+S6LYMKY3n89cn +sacrnsuYaOH8c+cQlLNBAI9jzerwm3WhV0xc/muUb3yfcUzTXxCXpKfizwms4dIY +dPmLID1MUjqORaBhKIyyzgRlPJoIzp7i9NVrAhq7TdhW73uk7lFFLkedABEBAAG0 +K0pvaG55IERvZSAoVGVzdCBrZXkgMikgPGpvaG55LmRvZUB0ZXN0LmNvbT6IvgQT +AQIAKAUCVbqCqAIbAwUJACeNAAYLCQgHAwIGFQgCCQoLBBYCAwECHgECF4AACgkQ +8ef033cVdY7PIAP+PUfTAdjyklsycxJYWPcfUQi/YS/GfhgPk5k02ktoCyYZD+ar +bOHvNxkYAXQwzJomx/XAWVcW2iFD5E/ObqmKP+lEf8Ef1/FRIWJja9qIZkRYZrkx +8DkHp7fcpZWI1MWBRd6DPbYFp4Pz4dCQKi+hO/EsQIMuUYn9o1ZJPxOUXgW4jQRV +uoKoAQQAtswckAKtMTz8MWZiD+iCPbiSLF3cWaTv3ZklB8952Nmzx7sfx1Kj7uzL +OyGnBH8p0USFIdpXLZqa0io1cZGms9hvHz0RybXQDu3Id363s6OrJY9XnGxyMwle +OTYHL9L6bkRmXbozkzjO22nZztJuviZywzTEASt7VcDpy3XwIXUAEQEAAYilBBgB +AgAPBQJVuoKoAhsMBQkAJ40AAAoJEPHn9N93FXWOuw0D/3kx4rNf5BDocA96L4kP +T9YrVgSs7N71wRPWxFrO2GCVP9K4jaflFxOZp93Me/wvJ7EcHB+R7LGFcaP29WfJ +h4bDszKoZFm40Wd9Uu7y72jXJdvp+oSRU8pFyBz4ZVRSSDJ6aVs4LeYihV0EOej3 +5syUccEA5wa96mTyAsj4/SzNmQGiBFXWOhkRBAChNwrx1m51/bQ1Bllu1vl3h73A +VBmZ9rl8kGbfOUw+PKUPmaTw9SvdIKj6U1sZC4XSTUL5Cj8C1Sgf6RfW2cFEw9NB +R9ux14/y3PfM8odxbdpcSenm1+Ak3SSysMNUHv6aDy49t+grLRrldcKEzaR9RPWJ +3mE+OTSU07SxBYNq7wCgoB5dEiK2gpKr3LcwlRQFoszDks8D/1jQA8cXnDAT0k5R +29OtsJauvnIf+qmnp/FD80sBllpLjVBU097fqBs5mlkmNay/s9Lv52LqliqCmUwT +A8KeitYVLq3P1qiGi+qIAMKwSbAZjZ35G9cMv6uAzXU3dCCwjluoyBY9hJFlEeOd +Oyq2C409dFH7DYUBFbGIIx5EfqTQA/9mVDd1XmHNuHkiIqsjPsCtfLPqCMiVqgkv +OtqewJgDlz46CRbPvMB3yr6mfumU7SRYJkkBTDov8fcWue+HRRVaPza/bWsQLHpr +rXlPr0F06mcA2l/j5f2IVzP9L/cQ1uwTOjD9McdaI7du9SIr75jjmymIk1vWbZ46 +I7RLeCIgz7QrSm9obnkgRG9lIChUZXN0IEtleSAyKSA8am9obnkuZG9lQHRlc3Qu +Y29tPohoBBMRAgAoBQJV1joZAhsDBQkAJ40ABgsJCAcDAgYVCAIJCgsEFgIDAQIe +AQIXgAAKCRA783LT0y38+z2fAKCYEhEuSP5pAzetik+c8eK1WuQsfwCeLIw20S+9 +YSYTpTn+D3ai48AnqRQ= +=n3ZW +-----END PGP PUBLIC KEY BLOCK----- diff --git a/keysign/gpg/keys/signonly.sec b/keysign/gpg/keys/signonly.sec new file mode 100644 index 0000000..2225bbc --- /dev/null +++ b/keysign/gpg/keys/signonly.sec @@ -0,0 +1,18 @@ +-----BEGIN PGP PRIVATE KEY BLOCK----- +Version: GnuPG v1 + +lQG7BFXWOhkRBAChNwrx1m51/bQ1Bllu1vl3h73AVBmZ9rl8kGbfOUw+PKUPmaTw +9SvdIKj6U1sZC4XSTUL5Cj8C1Sgf6RfW2cFEw9NBR9ux14/y3PfM8odxbdpcSenm +1+Ak3SSysMNUHv6aDy49t+grLRrldcKEzaR9RPWJ3mE+OTSU07SxBYNq7wCgoB5d +EiK2gpKr3LcwlRQFoszDks8D/1jQA8cXnDAT0k5R29OtsJauvnIf+qmnp/FD80sB +llpLjVBU097fqBs5mlkmNay/s9Lv52LqliqCmUwTA8KeitYVLq3P1qiGi+qIAMKw +SbAZjZ35G9cMv6uAzXU3dCCwjluoyBY9hJFlEeOdOyq2C409dFH7DYUBFbGIIx5E +fqTQA/9mVDd1XmHNuHkiIqsjPsCtfLPqCMiVqgkvOtqewJgDlz46CRbPvMB3yr6m +fumU7SRYJkkBTDov8fcWue+HRRVaPza/bWsQLHprrXlPr0F06mcA2l/j5f2IVzP9 +L/cQ1uwTOjD9McdaI7du9SIr75jjmymIk1vWbZ46I7RLeCIgzwAAnip1Qeh0f1r0 +v9jhMBt0zEJdjcZtCwm0K0pvaG55IERvZSAoVGVzdCBLZXkgMikgPGpvaG55LmRv +ZUB0ZXN0LmNvbT6IaAQTEQIAKAUCVdY6GQIbAwUJACeNAAYLCQgHAwIGFQgCCQoL +BBYCAwECHgECF4AACgkQO/Ny09Mt/Ps9nwCgmBIRLkj+aQM3rYpPnPHitVrkLH8A +niyMNtEvvWEmE6U5/g92ouPAJ6kU +=ZRn8 +-----END PGP PRIVATE KEY BLOCK----- diff --git a/keysign/gpg/keys/testkey1.pub b/keysign/gpg/keys/testkey1.pub new file mode 100644 index 0000000..3f36ef2 --- /dev/null +++ b/keysign/gpg/keys/testkey1.pub @@ -0,0 +1,31 @@ +-----BEGIN PGP PUBLIC KEY BLOCK----- +Version: GnuPG v1 + +mQENBFW6eXgBCADY8dYFEmz1hh4sjMNZ8bC5jjJaVE0pv34ATX0QJdaiLJZCzZeY ++Ly5Sndxi0Jx0DlmkXGQrDuPNBjJPr4wS5ea5CZG2VD8flq+GkgskawPRAQQ4xTs +Yrgoo7AwTJVK2E05c9/rdV+ksHXray2XJc1XsQ0gF4ZBbBTHXGzrj1jXxvuGNwm1 +AP7fSlQcBC90inSJVsMduYUFRGSihkTcSrENP7kYBGqL8DjQW2lzQdrtE1SFQtdR +uRyuvznGExu/UkuspAZTft235mYmi2VQNsYmZ3qc7vZ7IiyXsF+ixgtS4fzCgyFA +ZoQe5kFt9vX9/u/SFRJSB9LBeP/OVFI5jZoDABEBAAG0KUpvaG4gRG9lIChUZXN0 +IGtleSAxKSA8am9obi5kb2VAdGVzdC5jb20+iQE+BBMBAgAoBQJVunl4AhsDBQkA +J40ABgsJCAcDAgYVCAIJCgsEFgIDAQIeAQIXgAAKCRCbM8/Hu3Da+n5LB/9mL/vf +PJKAUxGC9fwiaPtB4avDBIgNbl3TpGZw9cPQ34Ze+B5chg4PwyLmhioCVHegtglR +SrhCgZcivoOq8jQKvc/hYrTZUB7TiQcCnvaPEDFtUbUpZHrfEAg9vr0CDIPjWnx8 +4e5ehiwA60LKsFyHyiE0ugltl89V8FxJmQfMUda6Nz3ZivScfpXJAsgMub3NRTJs +TBuTCIcc241gslCDp3SNfyE4JXTuMZlyGXiecrlane1xptbhqOVwelvJU0YbmwFg +zzUQ2yht0XPs5/5acVuYpSyPCzT3XrLbGo0sFyvPJi7WYGf4bvI9NyOxpDlUUKb1 +YVpkT4wqfPCx8HtVuQENBFW6eXgBCADhBtMPD6CHFn8MsuWjreBvVMCcDgQSSNOC +ieHsraUx93BarSYvHrp+qWpD//rE3rYp1MmZJTJt879bxnrEGOvVqcXqX4t8sGYj +tPR3Dndl/yvMBT/BGwHzGkjEGMSUby0bj/0IpbC4x+E0DMMn8C8TYONVGf5GpY7J +z37fev28Ya/Mt1QsWHrfLmeqwh71hrrAWgfM2hp/9meXSCCvTkriaKFRHJTlnemU +TEPvx5rI8lM4z5PuYehvLL2PkUGq0nK58QkAUIdGeiX1ych/EQrODxngBMHB4O91 +fU+RBaNmHZwMmYToBXa1CWqbSaBYuoyBFVGuNR3juEeVc5Lb1QHpABEBAAGJASUE +GAECAA8FAlW6eXgCGwwFCQAnjQAACgkQmzPPx7tw2vqhdQgA2GZKSYQjmsRSaEjP +/oqQhXsTsPTs4mNgUMgG2lLVi8+fiHWSLYfvrkNcaLT6RaKW3SqhFPMSMvf5WcgK +TIwmWlLPC+1dRcWw+JVOcTkgbZ9lWjP29XlJx30iM5pjVupB2/w7EZV/alAu2Tyv +7youIxROYi4rOt/7U+9qkctzRyJneWEi7YKCSAsqRuOp/lG20AcFdQLERYybbnhh +JVPHyMKItCj/TXMRxMVqz5xzLgDP3V7m/s3KpBriJJWhZE6mxMGphd1tdHHcVl+S +2MvqmwRdRDckMUt/6F5xYVX8YCU9HJHY29kflm6ElVbS4Du7GoP6yV/DytZJK/Me +LJaOZA== +=Dw+F +-----END PGP PUBLIC KEY BLOCK----- diff --git a/keysign/gpg/keys/testkey1.sec b/keysign/gpg/keys/testkey1.sec new file mode 100644 index 0000000..6d5bd67 --- /dev/null +++ b/keysign/gpg/keys/testkey1.sec @@ -0,0 +1,59 @@ +-----BEGIN PGP PRIVATE KEY BLOCK----- +Version: GnuPG v1 + +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 +=bM65 +-----END PGP PRIVATE KEY BLOCK----- diff --git a/keysign/gpg/keys/testkey2.pub b/keysign/gpg/keys/testkey2.pub new file mode 100644 index 0000000..4b1f7d1 --- /dev/null +++ b/keysign/gpg/keys/testkey2.pub @@ -0,0 +1,20 @@ +-----BEGIN PGP PUBLIC KEY BLOCK----- +Version: GnuPG v1 + +mI0EVbqCqAEEAKYNDbu4zCTOtOb8tnODaA7WnBHM8rSFFR4Hyd+S6LYMKY3n89cn +sacrnsuYaOH8c+cQlLNBAI9jzerwm3WhV0xc/muUb3yfcUzTXxCXpKfizwms4dIY +dPmLID1MUjqORaBhKIyyzgRlPJoIzp7i9NVrAhq7TdhW73uk7lFFLkedABEBAAG0 +K0pvaG55IERvZSAoVGVzdCBrZXkgMikgPGpvaG55LmRvZUB0ZXN0LmNvbT6IvgQT +AQIAKAUCVbqCqAIbAwUJACeNAAYLCQgHAwIGFQgCCQoLBBYCAwECHgECF4AACgkQ +8ef033cVdY7PIAP+PUfTAdjyklsycxJYWPcfUQi/YS/GfhgPk5k02ktoCyYZD+ar +bOHvNxkYAXQwzJomx/XAWVcW2iFD5E/ObqmKP+lEf8Ef1/FRIWJja9qIZkRYZrkx +8DkHp7fcpZWI1MWBRd6DPbYFp4Pz4dCQKi+hO/EsQIMuUYn9o1ZJPxOUXgW4jQRV +uoKoAQQAtswckAKtMTz8MWZiD+iCPbiSLF3cWaTv3ZklB8952Nmzx7sfx1Kj7uzL +OyGnBH8p0USFIdpXLZqa0io1cZGms9hvHz0RybXQDu3Id363s6OrJY9XnGxyMwle +OTYHL9L6bkRmXbozkzjO22nZztJuviZywzTEASt7VcDpy3XwIXUAEQEAAYilBBgB +AgAPBQJVuoKoAhsMBQkAJ40AAAoJEPHn9N93FXWOuw0D/3kx4rNf5BDocA96L4kP +T9YrVgSs7N71wRPWxFrO2GCVP9K4jaflFxOZp93Me/wvJ7EcHB+R7LGFcaP29WfJ +h4bDszKoZFm40Wd9Uu7y72jXJdvp+oSRU8pFyBz4ZVRSSDJ6aVs4LeYihV0EOej3 +5syUccEA5wa96mTyAsj4/SzN +=VcPb +-----END PGP PUBLIC KEY BLOCK----- diff --git a/keysign/gpg/keys/testkey2.sec b/keysign/gpg/keys/testkey2.sec new file mode 100644 index 0000000..fe2dde0 --- /dev/null +++ b/keysign/gpg/keys/testkey2.sec @@ -0,0 +1,35 @@ +-----BEGIN PGP PRIVATE KEY BLOCK----- +Version: GnuPG v1 + +lQH+BFW6gqgBBACmDQ27uMwkzrTm/LZzg2gO1pwRzPK0hRUeB8nfkui2DCmN5/PX +J7GnK57LmGjh/HPnEJSzQQCPY83q8Jt1oVdMXP5rlG98n3FM018Ql6Sn4s8JrOHS +GHT5iyA9TFI6jkWgYSiMss4EZTyaCM6e4vTVawIau03YVu97pO5RRS5HnQARAQAB +/gMDAgXUgrBgyliFYOBLoG2wBXS1UU3c03jQG7sAh0mDA/rXhy4mxVBqvp2r/tFZ ++MTcuVXp429Ys4PqmtaCislY6lRLMvp7MVP3rTL+nFjchnIp7S9806p8OQwABSPK +BN4qzkkFTswH5ElKDvbYQcP4QdWBRwpL38hWbrUhO3T0jG9CwLXE9chLZQ+AAi7K +DHUvSW7hhK/LogWyyDSTleS3fnn1CeS7/q6wJb/n41HXzQQFzV/dT7r4Mp6Ia/uy +JhC6/KITV9VQ8bGgUTpes2J/vxKXDQMVfmtunG0JzMyvalhFrIc0zAKLD+NufDag +F2QVQsnLNpcVWV4TQKbCr5pqcUJtPhhSVY4T+mFsuc9EDDhLVsc/ClWSsh39gvxC +Rm4YPbaNpb7P5laUrx3DOSq7vQEz88+hLGxbglYoEUxcUe+u/4D40lKePy9SUU7B +XDhe/HLK0XSQ+xe5+TVGQ4j7ChXwxPcC3xnczBB33r4ntCtKb2hueSBEb2UgKFRl +c3Qga2V5IDIpIDxqb2hueS5kb2VAdGVzdC5jb20+iL4EEwECACgFAlW6gqgCGwMF +CQAnjQAGCwkIBwMCBhUIAgkKCwQWAgMBAh4BAheAAAoJEPHn9N93FXWOzyAD/j1H +0wHY8pJbMnMSWFj3H1EIv2Evxn4YD5OZNNpLaAsmGQ/mq2zh7zcZGAF0MMyaJsf1 +wFlXFtohQ+RPzm6pij/pRH/BH9fxUSFiY2vaiGZEWGa5MfA5B6e33KWViNTFgUXe +gz22BaeD8+HQkCovoTvxLECDLlGJ/aNWST8TlF4FnQH+BFW6gqgBBAC2zByQAq0x +PPwxZmIP6II9uJIsXdxZpO/dmSUHz3nY2bPHux/HUqPu7Ms7IacEfynRRIUh2lct +mprSKjVxkaaz2G8fPRHJtdAO7ch3frezo6slj1ecbHIzCV45Ngcv0vpuRGZdujOT +OM7badnO0m6+JnLDNMQBK3tVwOnLdfAhdQARAQAB/gMDAgXUgrBgyliFYDOoohgh +0oB/cZzaZemiIQnggKuqcz8yUIvxyfDFnafazSJ9VVVKbtkrdG1X//g6wKs/LMIZ +kJmWQTL8zsHbsOtArA1mqXik88RD/D12O/mvGf/wjIGNhmBrXRY3YZ7g6FrGLi7t +TTJDduGGcYn+hI66PJMptqj9hTphY5fUGARajkrsjok4qs0BSBw6RzQSH3rJ51Gw +2L+w2xHQUm6bOcv7Tb3OdeIlK5jWEn/eoWukI8431up82SDBlzUesQHzBmEEJcAN +cBOFVjjBui+m3ESXrCZtXhOHiFniGb+gTvZjuXspr2/KXKQBjDbXBnBRspmbjVJi +4DDDJjpTxA/U+3N+4LpLOdMQ3oDOcJUzY10hiFY+Ybul2Wbd3UrPApzxiid3YLnL +MAp64yEJCRX3UzrEAVD912WVUJQWkYJafZUjOvy6ms3gDm6TI1Jddwm7xjuv9uy8 +yu3NiAbDw0FY5HX4VjeTiKUEGAECAA8FAlW6gqgCGwwFCQAnjQAACgkQ8ef033cV +dY67DQP/eTHis1/kEOhwD3oviQ9P1itWBKzs3vXBE9bEWs7YYJU/0riNp+UXE5mn +3cx7/C8nsRwcH5HssYVxo/b1Z8mHhsOzMqhkWbjRZ31S7vLvaNcl2+n6hJFTykXI +HPhlVFJIMnppWzgt5iKFXQQ56PfmzJRxwQDnBr3qZPICyPj9LM0= +=xYtv +-----END PGP PRIVATE KEY BLOCK----- diff --git a/keysign/gpg/test.py b/keysign/gpg/test.py new file mode 100644 index 0000000..f09ec01 --- /dev/null +++ b/keysign/gpg/test.py @@ -0,0 +1,133 @@ +#!/usr/bin/env python +# Copyright 2015 Andrei Macavei +# +# This file is part of GNOME Keysign. +# +# GNOME Keysign is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# GNOME Keysign is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with GNOME Keysign. If not, see . + +import os +from os.path import expanduser +import sys +import shutil +import tempfile + +import gpgme +import gpg + +import unittest + +from io import BytesIO, StringIO + +__all__ = ['GpgTestSuite'] + +keydir = os.path.join(os.path.dirname(__file__), 'keys') +gpg_default = os.environ.get('GNUPGHOME', os.path.join(expanduser("~"), '.gnupg')) + + +class GpgTestSuite(unittest.TestCase): + + def keyfile(self, key): + return open(os.path.join(keydir, key), 'rb') + + def test_set_engine(self): + ctx = gpgme.Context() + + tmpdir = tempfile.mkdtemp(prefix='tmp.gpghome') + gpg.set_engine(ctx, gpgme.PROTOCOL_OpenPGP, tmpdir) + + keys = [key for key in ctx.keylist()] + + self.assertEqual(len(keys), 0) + self.assertEqual(ctx.protocol, gpgme.PROTOCOL_OpenPGP) + + # clean temporary dir + shutil.rmtree(tmpdir, ignore_errors=True) + + def test_reset_engine(self): + ctx = gpgme.Context() + # set a temporary dir for gpg home + tmpdir = tempfile.mkdtemp(prefix='tmp.gpghome') + ctx.set_engine_info(gpgme.PROTOCOL_OpenPGP, None, tmpdir) + + # check if we have created the new gpg dir + self.assertTrue(os.path.isdir(tmpdir)) + gpg.reset_engine(ctx, tmpdir, gpgme.PROTOCOL_OpenPGP) + + self.assertEqual(gpgme.PROTOCOL_OpenPGP, ctx.protocol) + self.assertFalse(os.path.exists(tmpdir)) + + # clean temporary dir + shutil.rmtree(tmpdir, ignore_errors=True) + + def test_import_private_key(self): + ctx = gpgme.Context() + tmpdir = tempfile.mkdtemp(prefix='tmp.gpghome') + ctx.set_engine_info(gpgme.PROTOCOL_OpenPGP, None, tmpdir) + + gpg.import_private_key(ctx) + + # get the user's secret keys + default_ctx = gpgme.Context() + default_ctx.set_engine_info(gpgme.PROTOCOL_OpenPGP, None, gpg_default) + default_secret_keys = [key for key in default_ctx.keylist(None, True)] + + # compare the imported keys with the original secret keys + secret_keys = [key for key in ctx.keylist(None, True)] + + self.assertEqual(len(secret_keys), len(default_secret_keys)) + all_keys = sum(key1.subkeys[0].fpr != key2.subkeys[0].fpr for key1, key2 in zip(secret_keys, default_secret_keys)) + self.assertFalse(all_keys) + + # clean temporary dir + shutil.rmtree(tmpdir, ignore_errors=True) + + def test_import_keydata(self): + ctx = gpgme.Context() + tmpdir = tempfile.mkdtemp(prefix='tmp.gpghome') + ctx.set_engine_info(gpgme.PROTOCOL_OpenPGP, None, tmpdir) + + with self.keyfile('testkey1.pub') as fp: + keydata = fp.read() + + res = gpg.import_keydata(ctx, keydata) + self.assertTrue(res) + + # can we get the key ? + key = ctx.get_key('john.doe@test.com') + + def test_sign_uid(self): + ctx = gpgme.Context() + tmpdir = tempfile.mkdtemp(prefix='tmp.gpghome') + ctx.set_engine_info(gpgme.PROTOCOL_OpenPGP, None, tmpdir) + + with self.keyfile('testkey1.pub') as fp: + ctx.import_(fp) + + with self.keyfile('signonly.sec') as fp: + secret_key = fp.read() + + userId = ctx.get_key('john.doe@test.com').uids[0] + len_before = len(userId.signatures) + res = gpg.sign_uid(ctx, tmpdir, userId, secret_key) + + self.assertTrue(res) + + # verify if we have the uid signed + sigs = ctx.get_key('john.doe@test.com').uids[0].signatures + len_after = len(sigs) + self.assertGreater(len_after, len_before) + + +if __name__ == '__main__': + unittest.main() \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index 4582567..5f16e41 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,7 @@ # Debian packages -monkeysign==1.1 +python-gi +python-avahi +python-gpgme qrencode + +# Libs: python-dev, libqrencode-dev, libffi-dev, libssl-dev diff --git a/setup.py b/setup.py index c9ec051..90e8871 100644 --- a/setup.py +++ b/setup.py @@ -19,7 +19,8 @@ packages = [ 'keysign', 'keysign.compat', - 'keysign.network'], + 'keysign.network', + 'keysign.gpg'], #package_dir={'keysign': 'keysign'}, #package_data={'keysign': ['data/']}, data_files=[ @@ -35,7 +36,7 @@ # to not confuse Ubuntu 14.04's pip as that # seems incompatible with a newer requests library. # https://bugs.launchpad.net/ubuntu/+source/python-pip/+bug/1306991 - 'requests<=2.2', + 'requests<=2.2', 'qrencode', #'monkeysign', # Apparently not in the cheeseshop # avahi # Also no entry in the cheeseshop @@ -43,7 +44,7 @@ ], license='GPLv3+', long_description=open('README.rst').read(), - + entry_points = { #'console_scripts': [ # 'keysign = keysign.main' @@ -53,18 +54,18 @@ 'gks-qrcode = keysign.GPGQRCode:main', ], }, - + classifiers = [ # Maybe not yet... #'Development Status :: 4 - Beta', - + 'Intended Audience :: Developers', 'Intended Audience :: System Administrators', 'Intended Audience :: End Users/Desktop', 'Intended Audience :: Information Technology', 'Intended Audience :: Legal Industry', - 'Intended Audience :: Telecommunications Industry', - + 'Intended Audience :: Telecommunications Industry', + 'Programming Language :: Python', 'Programming Language :: Python :: 2', # I think we are only 2.7 compatible