#!/usr/bin/env python # GPG keys manager # Author: Christopher R. Gabriel # Licensed under the terms of the GNU GPL # # TODO: # preferences (keyserver, port, etc) # UI improvements from gnome.ui import * from gtk import * import GdkImlib import string, urllib, re, os,sys import smtplib #config version = "0.5.1" ## we're done here cfgfile = os.environ['HOME'] + "/.nofgpgrc" needconfig = 0 config = 0 def write_conf(cfgfile=cfgfile,keyserver="keyserver.linux.it",port="11371"): import ConfigParser cfp = ConfigParser.ConfigParser() cfp.add_section("NOFGPG") cfp.set("NOFGPG", "keyserver", keyserver) cfp.set("NOFGPG", "port", port) f = open(cfgfile, "w") cfp.write(f) f.close() def read_conf(cfgfile=cfgfile): import ConfigParser cfp = ConfigParser.ConfigParser() cfp.read(cfgfile) config = {} config['keyserver'] = cfp.get("NOFGPG", "keyserver") config['port'] = cfp.get("NOFGPG", "port") return config try: os.stat(cfgfile) except OSError: needconfig =1 write_conf() config = read_conf() class Configger: def __init__(self): logo = GdkImlib.Image("/usr/share/pixmaps/gnome-settings.png") self.win = GtkWindow() self.conf = GnomeDruid() self.win.add(self.conf) self.conf.show() self.conf.connect("cancel", self.write) page = GnomeDruidPageStart(title="NOFGPG Configuration",text="Welcome, here you can configure NOFGPG. \nPlease press the 'Next' button and Enjoy!",logo=logo) page.show() self.conf.append_page(page) label = GtkLabel("Keyserver (just type the host name, like 'pgp.mit.edu'") self.entry = GtkEntry() page = GnomeDruidPageStandard(title="Keyserver",logo=logo) vbox = page.__getattr__("vbox") vbox.pack_start(label) vbox.pack_start(self.entry) label.show() self.entry.show() page.show() self.conf.append_page(page) page = GnomeDruidPageFinish(title="And we're done!",text="Thanks, configuration complete.",logo=logo) page.connect("finish", self.write) page.show() self.conf.append_page(page) self.win.show() def write(self,cfp,keyserver="keyserver.linux.it",port="11371"): global config newkeyserver = self.entry.get_text() if(len(newkeyserver) <= 0): ks = keyserver else: ks = newkeyserver write_conf(keyserver=ks,port=port) self.win.destroy() config = read_conf() def cancel(self,cfp): self.write() self.win.destroy() if(needconfig ==1): conf = Configger() else: config = read_conf() class Crypter: def __init__(self): self.win = GtkDialog() frame = GtkFrame("Crypt something") self.win.vbox.pack_start(frame) frame.show() bar = GtkOptionMenu() frame.add(bar) bar.show() menu = GtkMenu() menuitem = GtkMenuItem("cip") menu.append(menuitem) menuitem.show() menuitem = GtkMenuItem("ciop") menu.append(menuitem) menuitem.show() menu.show() bar.set_menu(menu) self.win.show() class Importer: def __init__(self): self.file = None self.choices = {} self.win = GtkDialog() frame = GtkFrame("Import new key") self.win.vbox.pack_start(frame) frame.show() vbox = GtkVBox(spacing=3) frame.add(vbox) vbox.show() text = self.choices['text'] = GtkText() text.set_editable(TRUE) choice1 = GtkRadioButton(None, "Text") choice1.connect("toggled", self.set_sense, "text") vbox.pack_start(choice1) choice1.show() vbox.pack_start(text) text.show() file = self.choices['file'] = GtkButton("choose..") file.connect("clicked", self.select_file) file.set_sensitive(FALSE) choice2 = GtkRadioButton(choice1, "File") choice2.connect("toggled", self.set_sense, "file") vbox.pack_start(choice2) choice2.show() box = GtkHBox(spacing=3) vbox.pack_start(box) box.show() self.entry = GtkEntry() self.entry.set_sensitive(FALSE) box.pack_start(self.entry) self.entry.show() box.pack_start(file) file.show() button = GtkButton("close") button.connect("clicked", self.hide) self.win.action_area.pack_start(button) button.show() ib = GtkButton("import") ib.connect("clicked", self.do_import) self.win.action_area.pack_start(ib) ib.set_flags(CAN_DEFAULT) ib.grab_default() ib.show() def do_import(self,button): data = self.choices['text'].get_chars(0,-1) p = os.popen("gpg --import", 'w') p.write(data) res = p.close() self.hide(None) if(res == None): dlg = GnomeOkDialog("Key Imported!") dlg.set_modal(TRUE) dlg.run() else: dlg = GnomeErrorDialog("Errors importing the key!") dlg.set_modal(TRUE) dlg.run() def file_selection_ok(self, button, fs): self.choices['text'].delete_text(0,-1) self.entry.delete_text(0,-1) f = open(fs.get_filename(), "r") self.choices['text'].insert_defaults(f.read()) self.entry.set_text(fs.get_filename()) fs.hide() def select_file(self,button): win = GtkFileSelection("Choose the file containing the key") win.connect("delete_event", win.hide) win.ok_button.connect("clicked", self.file_selection_ok, win) win.cancel_button.connect("clicked", win.hide) win.show() def set_sense(self, button, arg): if(arg == "text"): self.choices['text'].set_sensitive(TRUE) self.choices['file'].set_sensitive(FALSE) self.entry.set_sensitive(FALSE) else: self.choices['text'].set_sensitive(FALSE) self.choices['file'].set_sensitive(TRUE) self.entry.set_sensitive(TRUE) def hide(self,foo): self.win.hide() def show(self): self.win.show() def import_new(button): importer = Importer() importer.show() def crypter_new(button): crypter = Crypter() class SecKeys: def __init__(self): self.ui = GtkVBox(spacing=3) self.sb = GtkScrolledWindow() self.sb.set_policy(POLICY_AUTOMATIC, POLICY_AUTOMATIC) self.ui.pack_start(self.sb) self.vbox = GtkVBox(spacing=3) self.sb.add_with_viewport(self.vbox) self.clist = GtkCList(4, ["Key Id", "Date", "Owner", "Email"]) self.clist.set_column_width(0, 100) self.clist.set_column_width(1, 70) self.clist.set_column_width(2, 150) self.vbox.pack_start(self.clist) self.clist.show() self.vbox.show() self.found_keys = [] self.sb.show() self.ui.show() def refresh(self,widget): self.clist.clear() self.display_keys() def display_keys(self): p = os.popen("gpg --list-secret-keys", 'r') data = string.strip(p.read()) res = p.close() if(res == None): data = string.replace(data, "\r", "") data = string.split(data, "\n") pub = re.compile("^sec.*") for datakey in data: match = pub.search(datakey) if(match != None): key = string.split(match.group()) key_data = [] key_data.append(key[1]) key_data.append(key[2]) key_data.append(string.join(key[3:-1])) key_data.append(key[-1]) self.clist.append(key_data) else: dlg = GnomeErrorDialog("Error fetching secret key list") dlg.set_modal(TRUE) dlg.run() class PubKeys: def __init__(self): self.ui = GtkVBox(spacing=3) self.sb = GtkScrolledWindow() self.s = None self.sb.set_policy(POLICY_AUTOMATIC, POLICY_AUTOMATIC) self.ui.pack_start(self.sb) self.vbox = GtkVBox(spacing=3) self.sb.add_with_viewport(self.vbox) self.clist = GtkCList(4, ["Key Id", "Date", "Owner", "Email"]) self.clist.connect("button_press_event", self.show_menu) self.clist.connect("select_row", self.select_item) self.clist.set_column_width(0, 100) self.clist.set_column_width(1, 70) self.clist.set_column_width(2, 150) self.vbox.pack_start(self.clist) self.clist.show() scroll = GtkScrolledWindow() scroll.set_policy(POLICY_AUTOMATIC, POLICY_AUTOMATIC) self.ui.pack_start(scroll) box = GtkVBox(spacing=3) box.show() scroll.show() scroll.add_with_viewport(box) self.clistsub = GtkCList(2, ["Owner", "Email"]) box.pack_start(self.clistsub) self.clistsub.show() self.menu = GtkMenu() menuitem = GtkMenuItem("Delete this key") menuitem.connect("activate", self.delete_key) self.menu.append(menuitem) menuitem.show() menuitem = GtkMenuItem("Export this key") menuitem.connect("activate", self.export_key) self.menu.append(menuitem) menuitem.show() menuitem = GtkMenuItem("Send him my public key") menuitem.connect("activate", self.send_my_key) self.menu.append(menuitem) menuitem.show() self.vbox.show() self.found_keys = [] self.sb.show() self.ui.show() def delete_key(self,button): if(self.s == None): dlg = GnomeErrorDialog("Please select the key to delete first!") dlg.set_modal(TRUE) dlg.run() elif (self.s == 0): dlg = GnomeErrorDialog("Do you really want to remove *YOUR* public key, dude?") dlg.set_modal(TRUE) dlg.run() else: keyid = string.split(self.clist.get_text(self.s,0), "/")[1] os.system("gpg --batch --yes --delete-key %s" % keyid) dlg = GnomeOkDialog("Key %s removed" % keyid) self.refresh(dlg) dlg.set_modal(TRUE) dlg.run() def export_key(self,button): if(self.s == None): dlg = GnomeErrorDialog("Please select the key to export first!") dlg.set_modal(TRUE) dlg.run() else: keyid = string.split(self.clist.get_text(self.s,0), "/")[1] keyname = string.replace(self.clist.get_text(self.s, 3), "<", "") keyname = string.replace(keyname, ">", "") export_file = os.environ['HOME'] + "/" + keyname + "-public-key" os.system("gpg --export -a %s -o %s" % (keyid,export_file)) dlg = GnomeOkDialog("Key %s exported to %s" % (keyid,export_file)) dlg.set_modal(TRUE) dlg.run() def send_my_key(self,button): key_from = string.replace(self.clist.get_text(0,3), "<", "") key_from = string.replace(key_from, ">", "") key_to = string.replace(self.clist.get_text(self.s,3), "<", "") key_to = string.replace(key_to, ">", "") p = os.popen("gpg --export -a %s" % self.clist.get_text(0,2)) data = "Hi, this is my public key! \n\n" + string.strip(p.read()) res = p.close() if(res == None): server = smtplib.SMTP('localhost') server.sendmail(key_from, key_to, data) server.quit() dlg = GnomeOkDialog("Public key sent to %s" % key_to) dlg.set_modal(TRUE) dlg.run() else: dlg = GnomeErrorDialog("Error fetching public key") dlg.set_modal(TRUE) dlg.run() def show_menu(self, _clist, event): if(event.button == 3): self.clist.select_row(self.clist.get_selection_info(event.x, event.y)[0],self.clist.get_selection_info(event.x, event.y)[1]) self.menu.popup(None, None, None , event.button, event.time) def select_item(self, _clist, r, c, event): self.s = r self.show_subuids() def show_subuids(self): self.clistsub.clear() keyid = string.split(self.clist.get_text(self.s,0), "/")[1] p = os.popen("gpg --list-keys %s" % keyid, 'r') data = string.strip(p.read()) res = p.close() if(res == None): data = string.replace(data, "\r", "") data = string.split(data, "\n") pub = re.compile("^uid.*") for datakey in data: match = pub.search(datakey) if(match != None): key = string.split(match.group()) key_data = [] key_data.append(string.join(key[1:-1])) key_data.append(key[-1]) self.clistsub.append(key_data) else: dlg = GnomeErrorDialog("Error fetching public key list") dlg.set_modal(TRUE) dlg.run() def refresh(self,widget): self.clist.clear() self.display_keys() def display_keys(self): p = os.popen("gpg --list-keys", 'r') data = string.strip(p.read()) res = p.close() if(res == None): data = string.replace(data, "\r", "") data = string.split(data, "\n") pub = re.compile("^pub.*") for datakey in data: match = pub.search(datakey) if(match != None): key = string.split(match.group()) key_data = [] key_data.append(key[1]) key_data.append(key[2]) key_data.append(string.join(key[3:-1])) key_data.append(key[-1]) self.clist.append(key_data) else: dlg = GnomeErrorDialog("Error fetching public key list") dlg.set_modal(TRUE) dlg.run() class Search: def __init__(self): global config self.path = "/pks/lookup?op=index&search=%s" self.opener = urllib.URLopener() self.s = None self.found_keys = [] self.founded = 0 self.ui = GtkVBox(spacing=3) self.hbox = GtkHBox(spacing=3) self.ui.pack_start(self.hbox, expand=FALSE, fill=FALSE) label = GtkLabel("Search for:") self.hbox.pack_start(label) label.show() self.entry = GtkEntry() self.entry.connect("activate", self.fetch) self.hbox.pack_start(self.entry) self.entry.show() self.search_b = GtkButton("Search!") self.search_b.connect("clicked", self.fetch) self.hbox.pack_start(self.search_b) self.search_b.show() self.import_key_b = GtkButton("Import!") self.hbox.pack_start(self.import_key_b) self.import_key_b.connect("clicked", self.import_selected_key) self.import_key_b.show() self.clear_b = GtkButton("Clear list") self.clear_b.connect("clicked", self.clear_list) self.hbox.pack_start(self.clear_b) self.clear_b.show() sb = GtkScrolledWindow() sb.set_policy(POLICY_AUTOMATIC, POLICY_AUTOMATIC) self.ui.pack_start(sb) sb.show() box = GtkVBox(spacing=3) sb.add_with_viewport(box) box.show() self.clist = GtkCList(4, ["Key Id", "Date", "Owner", "Email"]) self.clist.connect("select_row", self.select_item) self.clist.connect("unselect_row", self.unselect_item) box.pack_start(self.clist) self.hbox.show() self.ui.show() def clear_list(self, widget): self.clist.clear() self.found_keys = [] self.s = None self.founded = 0 def display_keys(self, source): reg = re.compile("<.*?>") data = reg.sub("", source) reg = re.compile(">") data = reg.sub(">", data) reg = re.compile("<") data = reg.sub("<", data) reg = re.compile(""") data = reg.sub("\"", data) reg = re.compile("&") data = reg.sub("\&", data) data = string.replace(data, "\r", "") result = string.split(data, "\n") result = result[6:] pub = re.compile(".*pub.*") for datakey in result: match = pub.search(datakey) if(match != None): self.founded = self.founded + 1 key = string.split(match.group()) key_data = [] self.clist.set_column_width(0, len(key[1] * 10)) key_data.append(key[1]) key_id = string.split(key[1], "/") self.found_keys.append((key_id[1:])) self.clist.set_column_width(1, len(key[2] * 10)) key_data.append(key[2]) self.clist.set_column_width(2, 100) key_data.append(string.join(key[3:-1])) self.clist.set_column_width(3, 100) key_data.append(key[-1]) self.clist.append(key_data) self.clist.show() dlg = GnomeOkDialog("%d keys found on %s" % (self.founded,config['keyserver'])) dlg.set_modal(TRUE) dlg.run() def import_selected_key(self,_button): if(self.s == None): dlg = GnomeErrorDialog("Please select the key to import first!") dlg.set_modal(TRUE) dlg.run() else: res = os.system("gpg --keyserver %s --recv-keys %s" % (config['keyserver'], str(self.found_keys[self.s])[2:-2])) if(res != 0): dlg = GnomeErrorDialog("Impossbile to fetch key %s" % str(self.found_keys[self.s])[2:-2]) dlg.set_modal(TRUE) dlg.run() else: dlg = GnomeOkDialog("Key %s successfully imported" % str(self.found_keys[self.s])[2:-2]) dlg.set_modal(TRUE) dlg.run() def select_item(self, _clist, r, c, event): self.s = r def unselect_item(self, _clist, r, c, event): self.s = None def fetch(self,widget): keys = self.entry.get_text() to_search = string.replace(keys, " ", "+") keyserver = ("http://%s:%s" % (config['keyserver'], config['port'])) url = ((keyserver+self.path) % to_search) try: f = self.opener.open(url) except IOError: dlg = GnomeErrorDialog("Can't connect to keyserver %s port %s!" %(config['keyserver'],config['port'])) dlg.set_modal(TRUE) dlg.run() return "" source = f.read() self.display_keys(source) pubkeys = PubKeys() seckeys = SecKeys() notebook = 0 def destroy(*args): mainquit() def about(button): GnomeAbout('NOFGPG', version, 'Distributed under the terms of the GPL', ['Christopher R. Gabriel '], ('Simple GPG key manager')).show() def refresh(button): if(notebook.get_current_page() == 0): pubkeys.refresh(button) if(notebook.get_current_page() == 1): seckeys.refresh(button) def run_config(button): conf = Configger() file_menu = [ UIINFO_ITEM_STOCK('Preferences', None, run_config, STOCK_MENU_PROP), UIINFO_ITEM_STOCK('Quit', None, destroy, STOCK_MENU_QUIT)] help_menu = [ UIINFO_ITEM_STOCK('About...', None, about, STOCK_MENU_ABOUT)] menus = [ UIINFO_SUBTREE('File', file_menu),UIINFO_SUBTREE('Help', help_menu) ] toolbar = [ UIINFO_ITEM_STOCK('Import', 'Import a new key', import_new, STOCK_PIXMAP_CONVERT), UIINFO_ITEM_STOCK('Crypt', 'Crypt a file', crypter_new, STOCK_PIXMAP_CONVERT), UIINFO_ITEM_STOCK('List keys', 'List the keys', refresh, STOCK_PIXMAP_REFRESH), UIINFO_ITEM_STOCK('Refresh list', 'Refresh the current key list', refresh, STOCK_PIXMAP_REFRESH) ] window = GnomeApp("NofGPG", "No One Fear GPG - NOFGPG") window.connect("destroy", destroy) window.connect("delete_event", destroy) notebook = GtkNotebook() notebook.set_tab_pos(POS_TOP) window.set_contents(notebook) label = GtkLabel("Public Keys") notebook.append_page(pubkeys.ui, label) label = GtkLabel("Secret Keys") notebook.append_page(seckeys.ui, label) label = GtkLabel("Search") search = Search() notebook.append_page(search.ui, label) notebook.show() window.create_menus(menus) window.create_toolbar(toolbar) window.show() mainloop()