aboutsummaryrefslogtreecommitdiffstats
path: root/lang/py3-pyme/examples/pygpa.py
diff options
context:
space:
mode:
Diffstat (limited to 'lang/py3-pyme/examples/pygpa.py')
-rwxr-xr-xlang/py3-pyme/examples/pygpa.py1457
1 files changed, 1457 insertions, 0 deletions
diff --git a/lang/py3-pyme/examples/pygpa.py b/lang/py3-pyme/examples/pygpa.py
new file mode 100755
index 00000000..341a87cb
--- /dev/null
+++ b/lang/py3-pyme/examples/pygpa.py
@@ -0,0 +1,1457 @@
+#!/usr/bin/env python3
+# $Id$
+# Copyright (C) 2005,2008 Igor Belyi <[email protected]>
+#
+# This program 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 2 of the License, or
+# (at your option) any later version.
+#
+# This program 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 this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+import gtk, gobject, gtk.glade
+import gettext
+import time, sys, os
+from pyme import errors, core
+from pyme.core import Context, Data, pubkey_algo_name
+from pyme.constants import validity, status, keylist, sig, sigsum
+
+# Enable internationalization using gpa translation pages.
+gettext.install('gpa', None, 1)
+gtk.glade.bindtextdomain('gpa')
+gtk.glade.textdomain('gpa')
+
+# Thanks to Bernhard Reiter for pointing out the following:
+# gpgme_check_version() necessary for initialisation according to
+# gpgme 1.1.6 and this is not done automatically in pyme-0.7.0
+print("gpgme version:", core.check_version())
+
+# Helper functions to convert non-string data into printable strings
+def sec2str(secs, empty="_(Unknown)"):
+ "Convert seconds since 1970 into mm/dd/yy string"
+ if secs > 0: return time.strftime("%m/%d/%y", time.localtime(secs))
+ elif secs == 0: return empty
+ else: return ""
+
+trusts = {
+ validity.UNDEFINED: "Unknown",
+ validity.NEVER: "Never",
+ validity.MARGINAL: "Marginal",
+ validity.FULL: "Full",
+ validity.ULTIMATE: "Ultimate"
+ }
+
+def validity2str(valid):
+ "Convert trust integer into a human understandable string"
+ if valid in trusts: return _("%s" % trusts[valid])
+ else: return _("Unknown")
+
+def keyvalid2str(key):
+ "Create a string representing validity of a key"
+ if key.owner_trust==validity.ULTIMATE:
+ return _("Fully Valid")
+ if key.revoked: return _("Revoked")
+ if key.expired: return _("Expired")
+ if key.disabled: return _("Disabled")
+ if not key.uids or key.uids[0].invalid: return _("Incomplete")
+ return _("Unknown")
+
+def subvalid2str(subkey):
+ "Create a string representing validity of a subkey"
+ if subkey.revoked: return _("Revoked")
+ if subkey.expired: return _("Expired")
+ if subkey.disabled: return _("Disabled")
+ if subkey.invalid: return _("Unsigned")
+ return _("Valid")
+
+def signstat2str(sig):
+ "Create a string representing validity of a signature"
+ status = _("Unknown")
+ if sig.status == 1: status = _("Valid")
+ elif sig.status == 2: status = _("Bad")
+ if sig.expired: status = _("Expired")
+ elif sig.revoked: status = _("Revoked")
+ return status
+
+def sigsum2str(summary):
+ if summary & sigsum.VALID: return _("Valid")
+ if summary & sigsum.RED: return _("Bad")
+ if summary & sigsum.KEY_MISSING: return _("Unknown Key")
+ if summary & sigsum.KEY_REVOKED: return _("Revoked Key")
+ if summary & sigsum.KEY_EXPIRED: return _("Expired Key")
+ return _("Key NOT valid")
+
+def class2str(cls):
+ "Convert signature class integer into a human understandable string"
+ if cls==0x10: return _("Generic")
+ if cls==0x11: return _("Persona")
+ if cls==0x12: return _("Casual")
+ if cls==0x13: return _("Positive")
+ return _("Unknown")
+
+def algo2str(algo):
+ "Convert algorithm integer into a human understandable string"
+ return pubkey_algo_name(algo)
+
+def fpr2str(fpr):
+ "Convert fpr string in a sparsely spaced string for easy reading"
+ result = []
+ while fpr:
+ result.append(fpr[:4])
+ fpr = fpr[4:]
+ return " ".join(result)
+
+# Helper functions for tasks unrelated to any class
+def obj2model(objs, columns):
+ "Create a model from the obj (key, subkey, or signature) using columns"
+ model = gtk.ListStore(*[x.ctype for x in columns])
+ for obj in objs:
+ model.append([x.cfunc(obj) for x in columns])
+ return model
+
+def labels2table(key_labels):
+ "Create a gtk.Table from an array of 2-tuples of strings"
+ table = gtk.Table(len(key_labels), 2)
+ for i, row in enumerate(key_labels):
+ if len(row) != 2:
+ raise ValueError("Unexpected number of rows in labels2table call")
+ label1 = gtk.Label(row[0])
+ label1.set_alignment(1.0, 0.5)
+ label2 = gtk.Label(row[1])
+ label2.set_alignment(0.0, 0.5)
+ label2.set_padding(5, 0)
+ table.attach(label1, 0, 1, i, i+1, gtk.FILL, gtk.FILL)
+ table.attach(label2, 1, 2, i, i+1, gtk.FILL, gtk.FILL)
+ return table
+
+status2str = {}
+for name in dir(status):
+ if not name.startswith('__') and name != "util":
+ status2str[getattr(status, name)] = name
+
+def editor_func(status, args, val_dict):
+ prompt = "%s %s" % (val_dict["state"], args)
+ if prompt in val_dict:
+ val_dict["state"] = val_dict[prompt][0]
+ return val_dict[prompt][1]
+ elif args and "ignore %s" % status2str[status] not in val_dict:
+ for error in ["error %s" % status2str[status], "error %s" % prompt]:
+ if error in val_dict:
+ raise errors.GPGMEError(val_dict[error])
+ sys.stderr.write(_("Unexpected status and prompt in editor_func: " +
+ "%s %s\n") % (status2str[status], prompt))
+ raise EOFError()
+ return ""
+
+common_dict = {
+ "state": "start",
+ "quit keyedit.save.okay": ("save", "Y"),
+ "ignore NEED_PASSPHRASE": None,
+ "ignore NEED_PASSPHRASE_SYM": None,
+ "ignore BAD_PASSPHRASE": None,
+ "ignore USERID_HINT": None
+ }
+
+def change_key_expire(context, key, date):
+ "Change key's expiration date to date"
+ val_dict = common_dict.copy()
+ val_dict.update({
+ "start keyedit.prompt": ("expire", "expire"),
+ "expire keygen.valid": ("date", date),
+ "date keyedit.prompt": ("quit", "quit")
+ })
+ out = Data()
+ context.op_edit(key, editor_func, val_dict, out)
+
+def change_key_trust(context, key, new_trust):
+ "Change key's trust to new_trust"
+ val_dict = common_dict.copy()
+ val_dict.update({
+ "start keyedit.prompt": ("trust", "trust"),
+ "trust edit_ownertrust.value": ("value", "%d" % new_trust),
+ "value edit_ownertrust.set_ultimate.okay": ("value", "Y"),
+ "value keyedit.prompt": ("quit", "quit")
+ })
+ out = Data()
+ context.op_edit(key, editor_func, val_dict, out)
+
+def sign_key(context, key, sign_key, local):
+ "Sign key using sign_key. Signature is exportable if local is False"
+ # Values copied from <gpg-error.h>
+ GPG_ERR_CONFLICT = 70
+ GPG_ERR_UNUSABLE_PUBKEY = 53
+ val_dict = common_dict.copy()
+ val_dict.update({
+ "start keyedit.prompt": ("sign", (local and "lsign") or "sign"),
+ "sign keyedit.sign_all.okay": ("sign", "Y"),
+ "sign sign_uid.expire": ("sign", "Y"),
+ "sign sign_uid.class": ("sign", "0"),
+ "sign sign_uid.okay": ("okay", "Y"),
+ "okay keyedit.prompt": ("quit", "quit"),
+ "error ALREADY_SIGNED": GPG_ERR_CONFLICT,
+ "error sign keyedit.prompt": GPG_ERR_UNUSABLE_PUBKEY
+ })
+ out = Data()
+ context.signers_clear()
+ context.signers_add(sign_key)
+ context.op_edit(key, editor_func, val_dict, out)
+
+def trigger_change_password(context, key):
+ "Trigger sequence of passphrase_cb callbacks to change password of the key"
+ val_dict = common_dict.copy()
+ val_dict.update({
+ "start keyedit.prompt": ("passwd", "passwd"),
+ "passwd keyedit.prompt": ("quit", "quit")
+ })
+ out = Data()
+ context.op_edit(key, editor_func, val_dict, out)
+
+# Helper classes whose instances are used in the major PyGpa class
+class KeyInfo:
+ """Helper class to represent key information in different views.
+ If KeyInfo instance is initialized with an integer as a key the views
+ correspond to a state when multiple or no keys are selected"""
+ def __init__(self, key, secret=None):
+ self.key = key
+ self.secret = secret
+
+ def key_print_labels(self, fpr=False):
+ "Create an array of 2-tuples for 'User Name' and 'Key ID' fields"
+ labels = []
+ if type(self.key) != int:
+ if self.key.uids:
+ labels.append((_("User Name:"), self.key.uids[0].uid))
+ for uid in self.key.uids[1:]:
+ labels.append(("", uid.uid))
+ if fpr:
+ labels += [(_("Fingerprint:"), fpr2str(self.key.subkeys[0].fpr))]
+ else:
+ labels += [(_("Key ID:"), self.key.subkeys[0].keyid[-8:])]
+ return labels
+
+ def key_expires_label(self):
+ return sec2str(self.key.subkeys[0].expires,_("never expires"))
+
+ def details(self):
+ "Create a widget for 'Details' notebook tab"
+ if type(self.key) == int:
+ if self.key:
+ details=gtk.Label(_("%d keys selected") % self.key)
+ else:
+ details=gtk.Label(_("No keys selected"))
+ details.set_alignment(0.5, 0)
+ return details
+
+ if self.secret:
+ header = _("The key has both a private and a public part")
+ else:
+ header = _("The key has only a public part")
+ key_info_labels = [("", header)]
+
+ if self.key.can_certify:
+ if self.key.can_sign:
+ if self.key.can_encrypt:
+ ability = _("The key can be used for certification, " +
+ "signing and encryption.")
+ else:
+ ability = _("The key can be used for certification and " +
+ "signing, but not for encryption.")
+ else:
+ if self.key.can_encrypt:
+ ability = _("The key can be used for certification and " +
+ "encryption.")
+ else:
+ ability = _("The key can be used only for certification.")
+ else:
+ if self.key.can_sign:
+ if self.key.can_encrypt:
+ ability = _("The key can be used only for signing and " +
+ "encryption, but not for certification.")
+ else:
+ ability = _("The key can be used only for signing.")
+ else:
+ if self.key.can_encrypt:
+ ability = _("The key can be used only for encryption.")
+ else:
+ ability = _("This key is useless.")
+ key_info_labels.append(("", ability))
+
+ key_info_labels += self.key_print_labels() + [
+ (_("Fingerprint:"), fpr2str(self.key.subkeys[0].fpr)),
+ (_("Expires at:"), self.key_expires_label()),
+ (_("Owner Trust:"), validity2str(self.key.owner_trust)),
+ (_("Key Validity:"), keyvalid2str(self.key)),
+ (_("Key Type:"), _("%s %u bits") % \
+ (algo2str(self.key.subkeys[0].pubkey_algo),self.key.subkeys[0].length)),
+ (_("Created at:"), sec2str(self.key.subkeys[0].timestamp))
+ ]
+
+ return labels2table(key_info_labels)
+
+ def sign_model(self):
+ "Create a model for ComboBox of uids in 'Signatures' notebook tab"
+ model = gtk.ListStore(str, gtk.ListStore)
+ if type(self.key) != int:
+ for uid in self.key.uids:
+ model.append([uid.uid, obj2model(uid.signatures,sign_columns)])
+ return model
+
+ def subkey_model(self):
+ "Create a model for TreeView in 'Subkeys' notebook tab"
+ if type(self.key) == int:
+ return gtk.ListStore(*[x.ctype for x in subkey_columns])
+ else:
+ return obj2model(self.key.subkeys, subkey_columns)
+
+class Column:
+ "Helper class to represent a column in a TreeView"
+ def __init__(self, name, ctype, cfunc, detail=False):
+ """Column(name, ctype, cfunc):
+ name - Name to use as a column header
+ ctype - type to use in a model definition for this column
+ cfunc - function retrieving column's infromation from an object
+ detail- indicate if it's a detail visible only in detailed view"""
+ self.name = name
+ self.ctype = ctype
+ self.cfunc = cfunc
+ self.detail = detail
+
+# Columns for the list of keys which can be used as default
+def_keys_columns = [
+ Column(_("Key ID"), str, lambda x,y: x.subkeys[0].keyid[-8:]),
+ Column(_("User Name"), str,
+ lambda x,y: (x.uids and x.uids[0].uid) or _("[Unknown user ID]")),
+ Column(None, gobject.TYPE_PYOBJECT, lambda x,y: KeyInfo(x,y))
+ ]
+
+# Columns for the list of all keys in the keyring
+keys_columns = [
+ Column("", str, lambda x,y: (y and "sec") or "pub"),
+ def_keys_columns[0],
+ Column(_("Expiry Date"), str,
+ lambda x,y: sec2str(x.subkeys[0].expires, _("never expires")), True),
+ Column(_("Owner Trust"),str,lambda x,y:validity2str(x.owner_trust),True),
+ Column(_("Key Validity"), str, lambda x,y: keyvalid2str(x), True)
+ ] + def_keys_columns[1:]
+
+# Columns for the list of signatures on a uid
+sign_columns = [
+ Column(_("Key ID"), str, lambda x: x.keyid[-8:]),
+ Column(_("Status"), str, lambda x: signstat2str(x)),
+ Column(_("Level"), str, lambda x: class2str(x.sig_class)),
+ Column(_("Local"), type(True), lambda x: x.exportable==0),
+ Column(_("User Name"), str, lambda x: x.uid or _("[Unknown user ID]"))
+ ]
+
+# Columns for the list of subkeys
+subkey_columns = [
+ Column(_("Subkey ID"), str, lambda x: x.keyid[-8:]),
+ Column(_("Status"), str, lambda x: subvalid2str(x)),
+ Column(_("Algorithm"), str, lambda x: algo2str(x.pubkey_algo)),
+ Column(_("Size"), str, lambda x: _("%u bits") % x.length),
+ Column(_("Expiry Date"), str,
+ lambda x: sec2str(x.expires, _("never expires"))),
+ Column(_("Can sign"), type(True), lambda x: x.can_sign),
+ Column(_("Can certify"), type(True), lambda x: x.can_certify),
+ Column(_("Can encrypt"), type(True), lambda x: x.can_encrypt),
+ Column(_("Can authenticate"), type(True), lambda x: x.can_authenticate)
+ ]
+
+file_columns = [
+ Column(_("File"), str, lambda x: x)
+ ]
+
+class PyGpa:
+ "Major class representing PyGpa application"
+ def popup(self, dialog, parent=None, title=None):
+ "Common way to popup a dialog defined in Glade"
+ dialog.set_transient_for(parent or self.main_window)
+ if title: dialog.set_title(title)
+ result = dialog.run()
+ dialog.hide()
+ return result
+
+ def file_popup(self, dialog, parent=None, title=None):
+ return self.popup(dialog, parent or self.filemanager_window, title)
+
+ def error_message(self, text, parent=None, title=_("Warning")):
+ "Pop up an error message dialog"
+ if type(text) == int:
+ text = errors.GPGMEError(text).getstring()
+ title = "GPGME error"
+ elif isinstance(text, errors.GPGMEError):
+ text = text.getstring()
+ title = "GPGME error"
+ self.error_label.set_text(text)
+ self.popup(self.error_dialog, parent, title)
+
+ def file_error_message(self, text, parent=None, title=_("Warning")):
+ self.error_message(text, parent or self.filemanager_window, title)
+
+ def info_message(self, text, parent=None, title=_("Information")):
+ "Pop up an information dialog"
+ self.info_label.set_text(text)
+ self.popup(self.info_dialog, parent, title)
+
+ def yesno_message(self, text, parent=None):
+ "Pop up a dialog requiring yes/no answer"
+ self.yesno_label.set_text(text)
+ return self.popup(self.yesno_dialog, parent,
+ _("Warning")) == gtk.RESPONSE_YES
+
+ def on_uid_list_changed(self, uid_list):
+ "this callback is called when uid selection is changed"
+ index = uid_list.get_active()
+ if index == -1:
+ self.sign_treeview.set_model(KeyInfo(0).sign_model())
+ else:
+ self.sign_treeview.set_model(uid_list.get_model()[index][1])
+
+ def get_selected_keys(self, treeview=None):
+ "Helper function to get all selected rows in a treeview"
+ if not treeview:
+ treeview = self.keys_treeview
+ model, rows = treeview.get_selection().get_selected_rows()
+ return [model[path] for path in rows]
+
+ def on_keys_changed(self, keys_treeview):
+ "this callback is called when key selection is changed"
+ selection = keys_treeview.get_selection()
+ count = selection.count_selected_rows()
+ if count == 1:
+ key_info = self.get_selected_keys()[0][-1]
+ else:
+ key_info = KeyInfo(count)
+
+ self.update_menu(key_info)
+
+ # Update Details tab of the notebook
+ old_child = self.details_view.get_child()
+ if old_child: self.details_view.remove(old_child)
+ self.details_view.add(key_info.details())
+ self.details_view.show_all()
+
+ # Update Subkeys tab of the notebook
+ self.subkeys_treeview.set_model(key_info.subkey_model())
+
+ # Update Signatures tab of the notebook
+ sign_model = key_info.sign_model()
+ self.uid_list.set_model(sign_model)
+ if len(sign_model) < 2:
+ self.uid_list_box.hide()
+ else:
+ self.uid_list_box.show_all()
+ self.uid_list.set_active(0)
+ self.on_uid_list_changed(self.uid_list)
+
+ def on_keys_button_press(self, obj, event):
+ "callback on a mouse press in the keys_treeview"
+ if event.button == 3:
+ self.popup_menu.popup(None, None, None, event.button, event.time)
+ return True
+ return False
+
+ def create_popup_menu(self):
+ "create the popup menu shown on right mouse click"
+ self.items = [
+ (gtk.ImageMenuItem(gtk.STOCK_COPY), self.on_copy_activate),
+ (gtk.ImageMenuItem(gtk.STOCK_PASTE), self.on_paste_activate),
+ (gtk.ImageMenuItem(gtk.STOCK_DELETE), self.on_delete_activate),
+ (gtk.SeparatorMenuItem(), None),
+ (gtk.MenuItem(_("_Sign Keys...")), self.on_sign_keys_activate),
+ (gtk.MenuItem(_("Set _Owner Trust...")),
+ self.on_set_owner_trust_activate),
+ (gtk.MenuItem(_("_Edit Private Key...")),
+ self.on_edit_private_key_activate),
+ (gtk.SeparatorMenuItem(), None),
+ (gtk.MenuItem(_("E_xport Keys...")), self.on_export_keys_activate)
+ ]
+ self.popup_menu = gtk.Menu()
+ for item, callback in self.items:
+ if callback: item.connect("activate", callback)
+ self.popup_menu.append(item)
+ self.popup_menu.show_all()
+
+ def update_menu(self, key_info):
+ "update sensitivity of menu items depending on what keys are selected"
+ # copy, delete, sign, trust, edit, export
+ if key_info.secret == None:
+ if key_info.key: # more than one key selected
+ values = ( True, True, True, False, False, True)
+ else: # no keys selected
+ values = (False, False, False, False, False, False)
+ elif key_info.secret:
+ if key_info.key == self.default_key: # default key seleted
+ values = ( True, True, False, True, True, True)
+ else: # secret (not default) key selected
+ values = ( True, True, True, True, True, True)
+ else: # public key selected
+ values = ( True, True, True, True, False, True)
+
+ for w,v in zip((self.copy, self.delete, self.sign_keys,
+ self.set_owner_trust, self.edit_private_key,
+ self.export_keys), values):
+ w.set_sensitive(v)
+ for w,v in zip((self.items[0][0], self.items[2][0], self.items[4][0],
+ self.items[5][0], self.items[6][0], self.items[8][0]),
+ values):
+ w.set_sensitive(v)
+
+ def setup_columns(self):
+ "Helper function to setup columns of different treeviews"
+ for treeview, columns in \
+ [(self.keys_treeview, keys_columns),
+ (self.sign_treeview, sign_columns),
+ (self.subkeys_treeview, subkey_columns),
+ (self.def_keys_treeview, def_keys_columns),
+ (self.sign_with_keys_treeview, def_keys_columns),
+ (self.encrypt_with_keys_treeview, def_keys_columns),
+ (self.files_treeview, file_columns)]:
+ for index, item in enumerate([x for x in columns if x.name!=None]):
+ if item.ctype == str:
+ renderer = gtk.CellRendererText()
+ attrs = {"text": index}
+ else:
+ renderer = gtk.CellRendererToggle()
+ attrs = {"active": index}
+ column = treeview.insert_column_with_attributes(
+ index, item.name, renderer, **attrs)
+ column.set_sort_column_id(index)
+ column.set_visible(not item.detail)
+
+ for index,item in enumerate([x for x in keys_columns if x.name!=None]):
+ if item.name and not item.detail:
+ renderer = gtk.CellRendererText()
+ column = gtk.TreeViewColumn(item.name, renderer, text=index)
+ column.set_sort_column_id(index)
+ self.encrypt_for_keys_treeview.append_column(column)
+
+ for treeview in [self.encrypt_with_keys_treeview, self.keys_treeview,
+ self.encrypt_for_keys_treeview, self.files_treeview,
+ self.sign_with_keys_treeview]:
+ treeview.get_selection().set_mode(gtk.SELECTION_MULTIPLE)
+ self.def_keys_treeview.get_selection().set_mode(gtk.SELECTION_SINGLE)
+
+ cell = gtk.CellRendererText()
+ self.uid_list.pack_start(cell, True)
+ self.uid_list.add_attribute(cell, 'text', 0)
+
+ model = gtk.ListStore(str, str)
+ for lines in [(_("days"), "d"), (_("weeks"), "w"),
+ (_("months"), "m"), (_("years"), "y")]:
+ model.append(lines)
+ self.new_expire_unit_combo.set_model(model)
+ self.new_expire_unit_combo.child.set_editable(False)
+ self.new_algorithm_combo.child.set_editable(False)
+
+ self.files_treeview.set_model(gtk.ListStore(str))
+
+ def setup_default_views(self):
+ "Setup initial values for different views"
+ self.update_default_keys()
+ self.on_advanced_mode_toggled(self.advanced_mode_rb)
+ self.create_popup_menu()
+ self.on_keys_changed(self.keys_treeview)
+
+ def load_keys(self):
+ "Download keys from the keyring"
+ context = Context()
+ sec_keys = {}
+ for key in context.op_keylist_all(None, 1):
+ sec_keys[key.subkeys[0].fpr] = 1
+ model = gtk.ListStore(*[x.ctype for x in keys_columns])
+ encrypt_model = gtk.ListStore(*[x.ctype for x in keys_columns])
+ context.set_keylist_mode(keylist.mode.SIGS)
+ for key in context.op_keylist_all(None, 0):
+ secret = key.subkeys[0].fpr in sec_keys
+ data = [x.cfunc(key, secret) for x in keys_columns]
+ if key.can_encrypt: encrypt_model.append(data)
+ model.append(data)
+ self.keys_treeview.set_model(model)
+ self.encrypt_for_keys_treeview.set_model(encrypt_model)
+
+ def set_default_key(self, key):
+ "Setup default key and update status bar with it"
+ self.default_key = key
+ self.status_uid.set_text((key.uids and key.uids[0].uid) or \
+ _("[Unknown user ID]"))
+ self.status_keyid.set_text(key.subkeys[0].keyid[-8:])
+
+ def on_default_keys_changed(self, treeview):
+ "This callback is called when default key is changed in Preferences"
+ model, rows = treeview.get_selection().get_selected_rows()
+ if model and rows:
+ self.set_default_key(model[rows[0]][-1].key)
+
+ def add_default_key(self, model, path, iter, def_model):
+ "Helper function to add secret keys to the list of possible defaults"
+ key = model[path][-1]
+ if key.secret:
+ def_model.append([x.cfunc(key.key,True) for x in def_keys_columns])
+
+ def add_sig_key(self, model, path, iter, sign_model):
+ "Helper function to add secret keys to the list of possible defaults"
+ key = model[path][-1].key
+ if key.can_sign:
+ sign_model.append([x.cfunc(key,True) for x in def_keys_columns])
+
+ def select_default_key(self, model, path, iter):
+ "Helper function to select current default key from the available list"
+ if model[path][-1].key == self.default_key:
+ self.def_keys_treeview.get_selection().select_path(path)
+
+ def update_default_keys(self):
+ "Update list of default keys"
+ model = gtk.ListStore(*[x.ctype for x in def_keys_columns])
+ self.keys_treeview.get_model().foreach(self.add_default_key, model)
+ self.def_keys_treeview.set_model(model)
+ model.foreach(self.select_default_key)
+ selection = self.def_keys_treeview.get_selection()
+ if selection.count_selected_rows() != 1:
+ selection.select_path((0,))
+ self.on_default_keys_changed(self.def_keys_treeview)
+ model = gtk.ListStore(*[x.ctype for x in def_keys_columns])
+ self.def_keys_treeview.get_model().foreach(self.add_sig_key, model)
+ self.sign_with_keys_treeview.set_model(model)
+ self.encrypt_with_keys_treeview.set_model(model)
+
+ def on_select_all_activate(self, obj):
+ "This callback is called when SelectAll menu item is selected"
+ self.keys_treeview.get_selection().select_all()
+
+ def on_file_preferences_activate(self, obj):
+ "Callback called when Preferences menu item is selected in filemanager"
+ self.show_preferences(self.filemanager_window)
+
+ def on_preferences_activate(self, obj):
+ "Callback called when Preferences menu item is selected in key editor"
+ self.show_preferences(None)
+
+ def show_preferences(self, parent):
+ "Show preferences positioning its window in the middle of the parent"
+ self.popup(self.preferences_dialog, parent)
+ self.def_keyserver = self.default_keyserver_combox.child.get_text()
+
+ def on_advanced_mode_toggled(self, radiobutton):
+ "This callback is called when Advanced Mode selection is changed"
+ if radiobutton.get_active():
+ self.subkeys_notebook_tab.show()
+ self.get_generate_params = self.get_advanced_generate_params
+ else:
+ self.subkeys_notebook_tab.hide()
+ self.get_generate_params = self.get_novice_generate_params
+
+ def popup_progress_dialog(self, label, parent):
+ self.progress_dialog.set_transient_for(parent)
+ self.progress_label.set_text(label)
+ self.progress_dialog.show_all()
+ gobject.timeout_add(100, self.update_progress)
+
+ def on_progress_cancel_clicked(self, obj):
+ self.progress_context.cancel()
+
+ def update_progress(self):
+ "Helper function to show progress while a work on a key is being done"
+ try:
+ status = self.progress_context.wait(0)
+ if status == None or self.progress_func(status):
+ self.new_progressbar.pulse()
+ return True
+ except errors.GPGMEError as exc:
+ self.error_message(exc)
+
+ self.progress_context = None
+ self.progress_func = None
+ self.progress_dialog.hide()
+
+ # Let callback to be removed.
+ return False
+
+ def key_generate_done(self, status):
+ "Helper function called on the completion of a key generation"
+ if status == 0:
+ fpr = self.progress_context.op_genkey_result().fpr
+ self.progress_context.set_keylist_mode(keylist.mode.SIGS)
+ key = self.progress_context.get_key(fpr, 0)
+ data = [x.cfunc(key, True) for x in keys_columns]
+ self.keys_treeview.get_model().append(data)
+ if key.can_encrypt:
+ self.encrypt_for_keys_treeview.get_model().append(data)
+ self.update_default_keys()
+ else:
+ self.error_message(status)
+ return False
+
+ def on_new_activate(self, obj):
+ "Callback for 'New Key' menu item"
+ params = self.get_generate_params()
+ if params == None:
+ return
+
+ (key_algo, subkeys, size, userid, email,
+ comment, expire, password, backup) = params
+
+ gen_string = "<GnupgKeyParms format=\"internal\">\n" + \
+ "Key-Type: %s\n" % key_algo + \
+ "Key-Length: %d\n" % size
+ if subkeys:
+ gen_string += "Subkey-Type: %s\n" % subkeys + \
+ "Subkey-Length: %d\n" % size
+ gen_string += "Name-Real: %s\n" % userid
+ if email:
+ gen_string += "Name-Email: %s\n" % email
+ if comment:
+ gen_string += "Name-Comment: %s\n" % comment
+ if expire:
+ gen_string += "Expire-Date: %s\n" % expire
+ if password:
+ gen_string += "Passphrase: %s\n" % password
+ gen_string += "</GnupgKeyParms>\n"
+
+ self.progress_context = Context()
+ self.progress_context.op_genkey_start(gen_string, None, None)
+ self.progress_func = self.key_generate_done
+ self.popup_progress_dialog(_("Generating Key..."), self.main_window)
+
+ def check_passphrase(self, passphrase, repeat_passphrase, parent):
+ """Helper function to check that enetered password satisfies our
+ requirements"""
+ if not passphrase:
+ self.error_message(_('You did not enter a passphrase.\n' +
+ 'It is needed to protect your private key.'),
+ parent)
+ elif repeat_passphrase != passphrase:
+ self.error_message(_('In "Passphrase" and "Repeat passphrase",\n' +
+ 'you must enter the same passphrase.'),
+ parent)
+ else:
+ return True
+ return False
+
+ def get_novice_generate_params(self):
+ "Helper function to get generate key parameter in Novice mode"
+ dialogs = [self.generate_userid_dialog,
+ self.generate_email_dialog,
+ self.generate_comment_dialog,
+ self.generate_passphrase_dialog,
+ self.generate_backup_dialog]
+ step = 0
+ params = None
+ while step>=0:
+ dialog = dialogs[step]
+ dialog.set_transient_for(self.main_window)
+ result = dialog.run()
+ newstep = step
+ if result == 2:
+ if step == 0:
+ userid = self.generate_novice_userid_entry.get_text()
+ if userid: newstep = step + 1
+ else: self.error_message(_("Please insert your name."))
+ elif step == 1:
+ email = self.generate_novice_email_entry.get_text()
+ if email: newstep = step + 1
+ else:
+ self.error_message(_("Please insert your email address"))
+ elif step == 2:
+ comment = self.generate_novice_comment_entry.get_text()
+ newstep = step + 1
+ elif step == 3:
+ passphrase=self.generate_novice_passphrase_entry.get_text()
+ if self.check_passphrase(
+ passphrase,
+ self.generate_novice_repeat_passphrase_entry.get_text(),
+ dialog):
+ newstep = step + 1
+ elif step == 4:
+ backup = self.generate_novice_backup_rb.get_active()
+ params = ("DSA", "ELG-E", 1024, userid, email,
+ comment, "", passphrase, backup)
+ newstep = -1
+ elif result == 1:
+ newstep = step - 1
+ else:
+ newstep = -1
+
+ if newstep != step:
+ dialog.hide()
+ step = newstep
+
+ self.generate_novice_userid_entry.set_text("")
+ self.generate_novice_email_entry.set_text("")
+ self.generate_novice_comment_entry.set_text("")
+ self.generate_novice_passphrase_entry.set_text("")
+ self.generate_novice_repeat_passphrase_entry.set_text("")
+ return params
+
+ def on_new_expire_on_rb_toggled(self, expireon_rb):
+ self.new_expire_calendar.set_sensitive(expireon_rb.get_active())
+
+ def on_new_expire_after_rb_toggled(self, expireafter_rb):
+ active = expireafter_rb.get_active()
+ self.new_expire_count_entry.set_sensitive(active)
+ self.new_expire_unit_combo.set_sensitive(active)
+
+ def get_advanced_generate_params(self):
+ "Helper function to get generate key parameter in Advanced mode"
+ params = None
+ self.new_expire_unit_combo.set_active(0)
+ self.new_algorithm_combo.set_active(0)
+ self.new_key_size_combo.set_active(1)
+ self.generate_dialog.set_transient_for(self.main_window)
+ while params == None and self.generate_dialog.run() == gtk.RESPONSE_OK:
+ passphrase = self.new_passphrase_entry.get_text()
+ if not self.check_passphrase(
+ passphrase,
+ self.new_repeat_passphrase_entry.get_text(),
+ self.generate_dialog):
+ continue
+ key_algo, subkeys = {
+ 'DSA and ElGamal (default)': ("DSA", "ELG-E"),
+ 'DSA (sign only)': ("DSA", ""),
+ 'RSA (sign only)': ("RSA", "")
+ }[self.new_algorithm_combo.child.get_text()]
+ try:
+ size = int(self.new_key_size_combo.child.get_text())
+ except ValueError:
+ self.new_key_size_combo.child.grab_focus()
+ continue
+ userid = self.new_userid_entry.get_text()
+ email = self.new_email_entry.get_text()
+ comment = self.new_comment_entry.get_text()
+ expire = ""
+ if self.new_expire_after_rb.get_active():
+ model = self.new_expire_unit_combo.get_model()
+ unit = model[(self.new_expire_unit_combo.get_active(),)][1]
+ try:
+ value = int(self.new_expire_count_entry.get_text())
+ except ValueError:
+ self.new_expire_count_entry.grab_focus()
+ continue
+ expire = "%d%s" % (value, unit)
+ elif self.new_expire_on_rb.get_active():
+ (year, month, day) = self.new_expire_calendar.get_date()
+ expire = "%04d-%02d-%02d" % (year, month+1, day)
+ params = (key_algo, subkeys, size, userid, email,
+ comment, expire, passphrase, False)
+ self.generate_dialog.hide()
+ self.new_passphrase_entry.set_text("")
+ self.new_repeat_passphrase_entry.set_text("")
+ return params
+
+ def del_key(self, key, treeview):
+ "Helper function to delete a key from a treeview list"
+ row_list = []
+ treeview.get_model().foreach(lambda m,p,i,l: l.append(m[p]), row_list)
+ for row in row_list:
+ if row[-1].key.subkeys[0].fpr == key.subkeys[0].fpr:
+ row.model.remove(row.iter)
+
+ def on_delete_activate(self, obj):
+ "Callback for 'Delete Keys' menu item"
+ message = {
+ True: _("This key has a secret key. Deleting this key cannot be"+
+ " undone, unless you have a backup copy."),
+ False: _("This key is a public key. Deleting this key cannot be "+
+ "undone easily, although you may be able to get a new " +
+ "copy from the owner or from a key server.")
+ }
+ keytag = self.delete_key_keyinfo
+ for row in self.get_selected_keys():
+ self.delete_key_label.set_text(message[row[-1].secret])
+ table = labels2table(row[-1].key_print_labels())
+ keytag.add(table)
+ keytag.show_all()
+ if self.popup(self.delete_key_dialog) == gtk.RESPONSE_YES:
+ context = Context()
+ context.op_delete(row[-1].key, 1)
+ if row[-1].key.can_encrypt:
+ self.del_key(row[-1].key, self.encrypt_for_keys_treeview)
+ row.model.remove(row.iter)
+ self.update_default_keys()
+ self.on_keys_changed(self.keys_treeview)
+ keytag.remove(table)
+
+ def password_cb(self, hint, desc, prev_bad, hook=None):
+ "Callback to setup verification of a passphrase"
+ if prev_bad:
+ header = _("Wrong passphrase, please try again:")
+ else:
+ header = _("Please enter the passphrase for the following key:")
+ self.password_prompt_label.set_text(header)
+ keyid, userid = hint.split(" ", 1)
+ table = labels2table([(_("User Name:"), userid),
+ (_("Key ID:"), keyid[-8:])])
+ self.password_prompt_keyinfo.add(table)
+ self.password_prompt_keyinfo.show_all()
+ password = None
+ if self.popup(self.password_prompt_dialog) == gtk.RESPONSE_OK:
+ password = self.password_prompt_entry.get_text()
+ self.password_prompt_keyinfo.remove(table)
+ self.password_prompt_entry.set_text("")
+ if not password:
+ GPG_ERR_CANCELED = 99
+ raise errors.GPGMEError(GPG_ERR_CANCELED)
+ return password
+
+ def password_change_cb(self, hint, desc, prev_bad, hook):
+ "Callback to setup for passphrase change"
+ if not prev_bad:
+ hook["count"] += 1
+
+ if hook["count"] == 1:
+ return self.password_cb(hint, desc, prev_bad)
+ else:
+ password = None
+ self.password_change_dialog.set_transient_for(self.main_window)
+ while password == None and \
+ self.password_change_dialog.run() == gtk.RESPONSE_OK:
+ password = self.password_change_passphrase.get_text()
+ if not self.check_passphrase(
+ password,
+ self.password_change_repeat_passphrase.get_text(),
+ self.password_change_dialog):
+ password = None
+ self.password_change_dialog.hide()
+ self.password_change_passphrase.set_text("")
+ self.password_change_repeat_passphrase.set_text("")
+ if not password:
+ GPG_ERR_CANCELED = 99
+ raise errors.GPGMEError(GPG_ERR_CANCELED)
+ return password
+
+ def on_sign_keys_activate(self, obj):
+ "Callback for 'Sign keys' menu item"
+ context = Context()
+ context.set_passphrase_cb(self.password_cb)
+ context.set_keylist_mode(keylist.mode.SIGS)
+ keytag = self.sign_key_keyinfo
+ for row in self.get_selected_keys():
+ if row[-1].key == self.default_key:
+ continue
+ if len(row[-1].key.uids) > 1:
+ self.sign_manyuids_label.show()
+ else:
+ self.sign_manyuids_label.hide()
+ table = labels2table(row[-1].key_print_labels(True))
+ keytag.add(table)
+ keytag.show_all()
+ if self.popup(self.sign_key_dialog) == gtk.RESPONSE_YES:
+ try:
+ sign_key(context, row[-1].key, self.default_key,
+ self.sign_locally_cb.get_active())
+ row[-1].key=context.get_key(row[-1].key.subkeys[0].fpr,0)
+ self.on_keys_changed(self.keys_treeview)
+ except errors.GPGMEError as exc:
+ self.error_message(exc)
+ keytag.remove(table)
+
+ def on_change_passphrase_clicked(self, obj, key_info):
+ "Callback for 'Change passphrase' button in editor for a private key"
+ try:
+ context = Context()
+ context.set_passphrase_cb(self.password_change_cb, {"count": 0})
+ trigger_change_password(context, key_info.key)
+ except errors.GPGMEError as exc:
+ self.error_message(exc)
+
+ def on_change_expiry_expireon_rb_toggled(self, expire_rb):
+ "Callback for 'never expire' radiobutton in editor for a private key"
+ self.change_expiry_calendar.set_sensitive(expire_rb.get_active())
+
+ def on_change_expiration_clicked(self, obj, key_info):
+ "Callback for 'Change expiration' button in editor for a private key"
+ if key_info.key.subkeys[0].expires:
+ year, month, day = time.localtime(key_info.key.subkeys[0].expires)[:3]
+ self.change_expiry_calendar.select_month(month-1, year)
+ self.change_expiry_calendar.select_day(day)
+ self.change_expiry_expireon_rb.set_active(True)
+ else:
+ self.change_expiry_never_rb.set_active(True)
+ if self.popup(self.change_expiry_dialog,
+ self.edit_key_dialog) == gtk.RESPONSE_OK:
+ year, month, day = self.change_expiry_calendar.get_date()
+ expire = "%04d-%02d-%02d" % (year, month+1, day)
+ try:
+ context = Context()
+ context.set_passphrase_cb(self.password_cb)
+ change_key_expire(context, key_info.key, expire)
+ context.set_keylist_mode(keylist.mode.SIGS)
+ key_info.key=context.get_key(key_info.key.subkeys[0].fpr,0)
+ self.on_keys_changed(self.keys_treeview)
+ self.edit_key_date_label.set_text(key_info.key_expires_label())
+ except errors.GPGMEError as exc:
+ self.error_message(exc)
+
+ def on_edit_private_key_activate(self, obj):
+ "Callback for 'Edit Private Key' menu item"
+ keys = self.get_selected_keys()
+ if len(keys) != 1 or not keys[0][-1].secret:
+ return
+
+ key_info = keys[0][-1]
+ table = labels2table(key_info.key_print_labels())
+ self.edit_key_date_label.set_text(key_info.key_expires_label())
+ self.edit_key_keyinfo.add(table)
+ self.edit_key_keyinfo.show_all()
+ connect1_id = self.edit_key_change_expiration.connect(
+ "clicked", self.on_change_expiration_clicked, key_info)
+ connect2_id = self.edit_key_change_passphrase.connect(
+ "clicked", self.on_change_passphrase_clicked, key_info)
+ self.popup(self.edit_key_dialog)
+ self.edit_key_change_expiration.disconnect(connect1_id)
+ self.edit_key_change_passphrase.disconnect(connect2_id)
+ self.edit_key_keyinfo.remove(table)
+
+ def on_set_owner_trust_activate(self, obj):
+ "Callback for 'Set Owner Trust' menu item"
+ keys = self.get_selected_keys()
+ if len(keys) != 1:
+ return
+
+ key_info = keys[0][-1]
+ table = labels2table(key_info.key_print_labels())
+ self.ownertrust_key.add(table)
+ self.ownertrust_key.show_all()
+ trust = key_info.key.owner_trust
+ if trust < 0 or trust not in trusts:
+ trust = validity.UNDEFINED
+ getattr(self, "ownertrust_"+trusts[trust]).set_active(True)
+ if self.popup(self.ownertrust_dialog) == gtk.RESPONSE_OK:
+ for trust, name in trusts.items():
+ if getattr(self, "ownertrust_"+name).get_active():
+ try:
+ context = Context()
+ change_key_trust(context, key_info.key, trust)
+ key_info.key.owner_trust = trust
+ self.on_keys_changed(self.keys_treeview)
+ except errors.GPGMEError as exc:
+ self.error_message(exc)
+ break
+ self.ownertrust_key.remove(table)
+
+ def import_keys_from_data(self, data):
+ "Helper function to import keys into application from a Data() object"
+ context = Context()
+ status = context.op_import(data)
+ if status:
+ self.error_message(status)
+ else:
+ result = context.op_import_result()
+ if result.considered == 0:
+ self.error_message(_("No keys were found."))
+ else:
+ self.load_keys()
+ self.info_message(_("%i public keys read\n" +
+ "%i public keys imported\n" +
+ "%i public keys unchanged\n" +
+ "%i secret keys read\n" +
+ "%i secret keys imported\n" +
+ "%i secret keys unchanged") % \
+ (result.considered,
+ result.imported,
+ result.unchanged,
+ result.secret_read,
+ result.secret_imported,
+ result.secret_unchanged))
+
+ def import_from_clipboard(self, clipboard, text, data):
+ "Callback to setup extraction of data from a clipboard"
+ if text:
+ self.import_keys_from_data(Data(text))
+
+ def on_paste_activate(self, obj):
+ "Callback for 'Paste' menu item"
+ gtk.clipboard_get().request_text(self.import_from_clipboard)
+
+ def on_import_keys_activate(self, obj):
+ "Callback for 'Import Keys' menu item"
+ import_file = None
+ dialog = self.import_file_dialog
+ dialog.set_transient_for(self.main_window)
+ while import_file == None and dialog.run() == gtk.RESPONSE_OK:
+ try:
+ import_file = open(dialog.get_filename(), "rb")
+ except IOError as strerror:
+ self.error_message(strerror, dialog)
+ import_file = None
+ dialog.hide()
+ if import_file != None:
+ self.import_keys_from_data(Data(file=import_file))
+ import_file.close()
+
+ def export_selected_keys(self, armor):
+ "Helper function to export selected keys into a Data() object"
+ context = Context()
+ context.set_armor(armor)
+ export_keys = Data()
+ for row in self.get_selected_keys():
+ context.op_export(row[-1].key.subkeys[0].fpr, 0, export_keys)
+ export_keys.seek(0,0)
+ return export_keys
+
+ def on_copy_activate(self, obj):
+ "Callback for 'Copy' menu item"
+ if self.keys_treeview.get_selection().count_selected_rows() > 0:
+ export_keys = self.export_selected_keys(True)
+ gtk.clipboard_get().set_text(export_keys.read())
+
+ def verify_output(self, filename, parent):
+ "Helper function to verify that user can write into the filename"
+ if os.path.exists(filename):
+ if os.path.isdir(filename):
+ self.error_message(_("%s is a directory")%filename, parent)
+ return False
+ else:
+ return self.yesno_message(_("The file %s already exists.\n" +
+ "Do you want to overwrite it?") %
+ filename, parent)
+ return True
+
+ def on_export_keys_activate(self, obj):
+ "Callback for 'Export Keys' menu item"
+ if self.keys_treeview.get_selection().count_selected_rows() < 1:
+ return
+
+ export_file = None
+ dialog = self.export_file_dialog
+ dialog.set_transient_for(self.main_window)
+ while export_file == None and dialog.run() == gtk.RESPONSE_OK:
+ filename = dialog.get_filename()
+ if self.verify_output(filename, dialog):
+ try:
+ export_file = open(filename, "wb")
+ except IOError as strerror:
+ self.error_message(strerror, dialog)
+ export_file = None
+ dialog.hide()
+ if export_file == None:
+ return
+
+ export_keys = self.export_selected_keys(export_armor_cb.get_active())
+ export_file.write(export_keys.read())
+ export_file.close()
+
+ def on_files_changed(self, obj):
+ "Callback called when selection of files in filemanager is changed"
+ if self.files_treeview.get_selection().count_selected_rows() < 1:
+ value = False
+ else:
+ value = True
+ for item in (self.sign, self.verify, self.encrypt, self.decrypt):
+ item.set_sensitive(value)
+
+ def open(self, filename, complain=False):
+ "Helper function to add a file into filemanager treeview"
+ model = self.files_treeview.get_model()
+ row_list = []
+ model.foreach(lambda m,p,i,l: l.append(m[p][0]), row_list)
+ if filename in row_list:
+ if complain:
+ self.file_error_message(_("The file is already open."))
+ else:
+ item = model.append([filename])
+ self.files_treeview.get_selection().select_iter(item)
+ self.on_files_changed(None)
+
+ def on_open_activate(self, obj):
+ "Callback for 'Open' menu item"
+ if self.file_popup(self.open_file_dialog) == gtk.RESPONSE_OK:
+ self.add_file(self.open_file_dialog.get_filename(), True)
+ self.open_file_dialog.unselect_all()
+
+ def get_selected_files(self):
+ "Helper function to return selected rows in filemanager treeview"
+ return self.get_selected_keys(self.files_treeview)
+
+ def on_clear_activate(self, obj):
+ "Callback for 'Clear' menu item"
+ for row in self.get_selected_files():
+ row.model.remove(row.iter)
+
+ def process_file_start(self, in_name, out_name):
+ "Helper function to start asynchronous processing of one file"
+ if self.verify_output(out_name, self.filemanager_window):
+ try:
+ self.in_data = Data(file=in_name)
+ self.out_data = Data()
+ self.out_name = out_name
+ self.file_func(self.in_data, self.out_data)
+ except errors.GPGMEError as exc:
+ self.file_error_message(exc)
+
+ def process_file_done(self, status):
+ "The function called when asynchronous processing of the file is done."
+ try:
+ errors.errorcheck(status)
+ self.out_data.seek(0,0)
+ out_file = file(self.out_name, "wb")
+ out_file.write(self.out_data.read())
+ out_file.close()
+ self.add_file(self.out_name)
+ if self.file_list:
+ self.process_file_start(*(self.file_list.pop(0)))
+ return True
+ except (errors.GPGMEError, IOError) as exc:
+ self.file_error_message(exc)
+
+ # Let python to free the memory.
+ self.out_data = None
+ self.in_data = None
+ self.out_name = None
+ self.file_list = []
+ self.file_func = None
+ return False
+
+ def process_files_async(self, file_list, func, label):
+ "Helper function to initialize async processing of the file list"
+ self.file_list = file_list
+ self.file_func = func
+ self.progress_func = self.process_file_done
+ self.process_file_start(*(self.file_list.pop(0)))
+ self.popup_progress_dialog(label, self.filemanager_window)
+
+ def on_sign_activate(self, obj):
+ "Callback for 'Sign' menu item"
+ files = self.get_selected_files()
+ if not files: return
+
+ if self.file_popup(self.sign_dialog) == gtk.RESPONSE_OK:
+ context = Context()
+ context.set_passphrase_cb(self.password_cb)
+ context.set_armor(self.sign_armor_cb.get_active())
+ for rw in self.get_selected_keys(self.sign_with_keys_treeview):
+ context.signers_add(rw[-1].key)
+ for cb,md,ext in [(self.sign_normal, sig.mode.NORMAL, ".gpg"),
+ (self.sign_clear, sig.mode.CLEAR, ".asc"),
+ (self.sign_separate,sig.mode.DETACH,".sig")]:
+ if cb.get_active():
+ sigmode = md
+ sigext = ext
+ break
+ self.progress_context = context
+ def sign(x,y):self.progress_context.op_sign_start(x,y,sigmode)
+ self.process_files_async([(f[0], f[0]+sigext) for f in files],
+ sign, _("Signing..."))
+
+ def verify_file_start(self, in_name, out_name):
+ "Helper function to start file signature verification process"
+ try:
+ self.in_name = in_name
+ self.out_name = out_name
+ self.signed = Data(file=self.in_name)
+ if out_name:
+ self.plain1 = Data(file=self.out_name)
+ self.plain2 = None
+ else:
+ self.plain1 = None
+ self.plain2 = Data()
+ self.progress_context.op_verify_start(self.signed, self.plain1,
+ self.plain2)
+ except errors.GPGMEError as exc:
+ self.file_error_message(exc)
+
+ def verify_file_done(self, status):
+ "The function called when asynchronous file signature verify is done."
+ try:
+ errors.errorcheck(status)
+ result = self.progress_context.op_verify_result()
+
+ model = gtk.ListStore(str, str, str)
+ treeview = gtk.TreeView(model)
+ treeview.set_rules_hint(True)
+ for index, title in enumerate([_("Key ID"), _("Status"),
+ _("User Name")]):
+ treeview.append_column(gtk.TreeViewColumn(
+ title, gtk.CellRendererText(), text=index))
+ for sign in result.signatures:
+ key = self.progress_context.get_key(sign.fpr, 0)
+ if key and key.uids:
+ keyid = key.subkeys[0].keyid[-8:]
+ userid = key.uids[0].uid
+ else:
+ keyid = sign.fpr[-8:]
+ userid = _("[Unknown user ID]")
+ model.append([keyid, sigsum2str(sign.summary), userid])
+
+ vbox = gtk.VBox()
+ if self.out_name:
+ vbox.add(gtk.Label(_("Verified data in file: %s") %
+ self.out_name))
+ label = gtk.Label(_("Signatures:"))
+ label.set_alignment(0, 1)
+ vbox.add(label)
+ vbox.add(treeview)
+ self.verified.append((vbox, gtk.Label(self.in_name)))
+ if self.file_list:
+ self.verify_file_start(*(self.file_list.pop(0)))
+ return True
+ except errors.GPGMEError as exc:
+ self.file_error_message(exc)
+
+ # Let python to free the memory.
+ self.signed = None
+ self.plain1 = None
+ self.plain2 = None
+ self.in_name = None
+ self.out_name = None
+ self.file_list = []
+ self.progress_dialog.hide()
+
+ if self.verified:
+ notebook = gtk.Notebook()
+ for page in self.verified: notebook.append_page(*page)
+ self.verify_result.add(notebook)
+ self.verify_result.show_all()
+ self.file_popup(self.verify_dialog)
+ self.verify_result.remove(notebook)
+ self.verified = []
+
+ return False
+
+ def on_verify_activate(self, obj):
+ "Callback for 'Verify' menu item"
+ files = self.get_selected_files()
+ if not files: return
+
+ self.file_list = []
+ for onefile in files:
+ in_name = onefile[0]
+ if in_name[-4:] == ".sig":
+ out_name = in_name[:-4]
+ elif in_name[-5:] == ".sign":
+ out_name = in_name[:-5]
+ else:
+ out_name = None
+ self.file_list.append((in_name, out_name))
+ self.verified = []
+ self.progress_context = Context()
+ self.progress_func = self.verify_file_done
+ self.verify_file_start(*(self.file_list.pop(0)))
+ self.popup_progress_dialog(_("Verifying..."), self.filemanager_window)
+
+ def on_encrypt_sign_toggled(self, cb):
+ "Callback for change of the 'Sign' check box in 'Encrypt files' dialog"
+ self.encrypt_with_keys_treeview.set_sensitive(cb.get_active())
+
+ def on_encrypt_activate(self, obj):
+ "Callback for 'Encrypt' menu item"
+ files = self.get_selected_files()
+ if not files: return
+
+ self.on_encrypt_sign_toggled(self.encrypt_sign_cb)
+ if self.file_popup(self.encrypt_dialog) == gtk.RESPONSE_OK:
+ context = Context()
+ context.set_passphrase_cb(self.password_cb)
+ if self.encrypt_armor_cb.get_active():
+ context.set_armor(True)
+ ext = ".asc"
+ else:
+ context.set_armor(False)
+ ext = ".gpg"
+ keylist = [row[-1].key for row in self.get_selected_keys(
+ self.encrypt_for_keys_treeview)]
+ if self.encrypt_sign_cb.get_active():
+ for row in self.get_selected_keys(
+ self.encrypt_with_keys_treeview):
+ context.signers_add(row[-1].key)
+ def encrypt(x,y):
+ self.progress_context.op_encrypt_sign_start(
+ keylist, 1, x, y)
+ else:
+ def encrypt(x,y):
+ self.progress_context.op_encrypt_start(
+ keylist, 1, x, y)
+ self.progress_context = context
+ self.process_files_async([(f[0], f[0]+sigext) for f in files],
+ encrypt, _("Encrypting..."))
+
+ def on_decrypt_activate(self, obj):
+ "Callback for 'Decrypt' menu item"
+ files = self.get_selected_files()
+ if not files: return
+
+ file_list = []
+ for onefile in self.get_selected_files():
+ in_name = onefile[0]
+ if in_name[-4:] in [".asc", ".gpg", ".pgp"]:
+ out_name = in_name[:-4]
+ else:
+ out_name = in_name + ".txt"
+ file_list.append((in_name, out_name))
+ self.process_context = Context()
+ self.process_files_async(file_list,
+ self.process_context.op_decrypt_start,
+ _("Decrypting..."))
+
+ def on_select_all_files_activate(self, obj):
+ "Callback for 'Select All' menu item in filemanager"
+ self.files_treeview.get_selection().select_all()
+
+ def on_keyring_editor_activate(self, obj):
+ "Callback for 'Keyring Editor' menu item"
+ self.main_window.show()
+
+ def on_keyring_editor_close_activate(self, obj, event=None):
+ "Callback for 'Close' menu item in Keyring Editor"
+ if self.filemanager_window.get_property("visible"):
+ self.main_window.hide()
+ return True
+ else:
+ self.on_quit_activate(None)
+
+ def on_filemanager_activate(self, obj):
+ "Callback for 'Filemanager' menu item"
+ self.on_files_changed(None)
+ self.filemanager_window.show()
+
+ def on_filemanager_close_activate(self, obj, event=None):
+ "Callback for 'Close' menu item in Filemanager"
+ if self.main_window.get_property("visible"):
+ self.filemanager_window.hide()
+ return True
+ else:
+ self.on_quit_activate(None)
+
+ def on_about_activate(self, obj):
+ "Callback for 'About' menu item"
+ self.popup(self.about_dialog)
+
+ def __repr__(self):
+ return self.__class__.__name__
+
+ def __getattr__(self, name):
+ "Dynamic retrieval of widgets from the glade XML"
+ if name.startswith("on_"):
+ self.__dict__[name] = lambda x: sys.stderr.write(
+ _("Callback %s is not implimented yet\n") % name)
+ elif name.startswith("_"):
+ return None
+ else:
+ self.__dict__[name] = self.wtree.get_widget(name)
+ return self.__dict__[name]
+
+ def __init__(self, path):
+ "PyGpa(path) - path is where pygpa.glade file can be found"
+ gladefile = os.path.join(path, "pygpa.glade")
+ self.wtree = gtk.glade.XML(gladefile, None, gtk.glade.textdomain())
+ self.wtree.signal_autoconnect(self)
+
+ self.default_key = None
+ self.load_keys()
+ self.setup_columns()
+ self.setup_default_views()
+ self.in_progress = {}
+
+ gtk.main()
+
+ def on_quit_activate(self, obj):
+ gtk.main_quit()
+
+PyGpa(os.path.dirname(sys.argv[0]))