From 04e3a84da349e2992e13258868fc10324455cfb0 Mon Sep 17 00:00:00 2001 From: oepen Date: Sat, 17 Jul 2010 11:35:31 +0000 Subject: [PATCH] -Lots of refactoring -Only start the card polling thread when the pinpad is shown -Added an info button to all entrys in the CertificateDescriptionWindow that might not be clear to the user git-svn-id: https://vsmartcard.svn.sourceforge.net/svnroot/vsmartcard@226 96b47cad-a561-4643-ad3b-153ac7d7599c --- pace-gui/src/pace-gui.py | 114 ++++++++++++++++++---------- pace-gui/src/pace_gui_globals.py.in | 112 +++++++++++++++++---------- 2 files changed, 146 insertions(+), 80 deletions(-) diff --git a/pace-gui/src/pace-gui.py b/pace-gui/src/pace-gui.py index 1482acb..de93b02 100644 --- a/pace-gui/src/pace-gui.py +++ b/pace-gui/src/pace-gui.py @@ -7,16 +7,17 @@ try: import pygtk pygtk.require("2.0") import gtk, gobject, glib, pango -except: +except ImportError: sys.exit(1) try: import pace -except: +except ImportError: print >> sys.stderr, "Could not import pace module, please install pyPACE" sys.exit(1) -from pace_gui_globals import * +from pace_gui_globals import AT_CHAT_STRINGS, STR_CARD_FOUND, STR_NO_CARD, IMAGES #FIXME:Fuckup +import pace_gui_globals as gui_globals class MokoWindow(gtk.Window): """ @@ -36,7 +37,7 @@ class MokoWindow(gtk.Window): assert(isinstance(self.title_str, basestring)) self.connect("destroy", gtk.main_quit) - #Display resolution of OpenMoko minus height of the SHR toolbar + #Display resolution of OpenMoko minus height of the SHR toolbar self.set_size_request(480, 640) #self.set_resizable(False) @@ -101,6 +102,9 @@ class MokoWindow(gtk.Window): self.hide() class CertificateDescriptionWindow(MokoWindow): + """This window is used to display information about the service provider, + extracted from the terminal certificate and the appendant certificate + description""" def __init__(self, binDescription, binCert): super(CertificateDescriptionWindow, self).__init__("Dienstanbieter", None) @@ -208,8 +212,8 @@ class CVCWindow(MokoWindow): chat = pace.cvc_get_chat(cvc) self.chat = pace.get_binary_chat(chat) self.rel_auth = [] - for c in self.chat: - self.rel_auth.append(ord(c)) + for char in self.chat: + self.rel_auth.append(ord(char)) self.rel_auth_len = len(self.rel_auth) #Extract the access rights from the CHAT and display them in the window @@ -219,7 +223,7 @@ class CVCWindow(MokoWindow): if (i % 8 == 0): j += 1 if self.rel_auth[self.rel_auth_len - j] & (1 << (i % 8)): - chk = customCheckButton(AT_CHAT_STRINGS[i], i, self.body) + chk = customCheckButton(i, self.body) self.access_rights.append(chk) def btnForward_clicked(self, widget, data=None): @@ -238,11 +242,13 @@ class CVCWindow(MokoWindow): self.hide() def __formatHexString(self, int_list): + """Take a list of integers and convert it to a hex string, where the + individual bytes are seperated by a colon (e.g. "0f:00:00")""" hex_str = "" for i in int_list: - c = hex(i)[2:] - if len(c) == 1: c = "0" + c - hex_str += c + ":" + byte = hex(i)[2:] + if len(byte) == 1: byte = "0" + byte + hex_str += byte + ":" return hex_str[:-1] @@ -251,26 +257,41 @@ class customCheckButton(object): The main difference isthat the checkbox can be placed at the right side of the label and that we can store an index with the button""" - def __init__(self, label, index, vbox): + def __init__(self, index, vbox): + self.idx = index + #Fetch name and (possibly empty) helptext + strings = AT_CHAT_STRINGS[index] + self.label = strings[0] + self.helptext = strings[1] + #Setup a label with the name of the access right - self.lbl = gtk.Label(label) + self.lbl = gtk.Label(self.label) self.lbl.set_alignment(0.0, 0.5) self.lbl.set_padding(20, 0) self.lbl.modify_font(pango.FontDescription("bold")) - #an image at the left side of the label - self.img = gtk.Image() - self.img.set_from_file(image_dir + "/info.png") + #If we have a helptext, put an image at the left side of the label + self.info_box = None + if self.helptext is not None: + self.info_box = gtk.EventBox() + img = gtk.Image() + img.set_from_file(IMAGES["info"]) + self.info_box.add(img) + self.info_box.connect("button_release_event", self._show_info) #...and a checkbox on the right side of the label - self.idx = index self.chk = gtk.CheckButton("") self.chk.set_active(True) self.chk.set_alignment(1.0, 0.5) #Insert the label and the checkbox in the vbox and add a seperator hbox = gtk.HBox() - hbox.pack_start(self.img, False, False) + if self.info_box is not None: + hbox.pack_start(self.info_box, False, False) + else: #pack empty label for spacing + spacer = gtk.Label("") + spacer.set_size_request(32, 32) + hbox.pack_start(spacer, False, False) hbox.pack_start(self.lbl, True, True) hbox.pack_start(self.chk, False, False) vbox.pack_start(gtk.HSeparator()) @@ -279,6 +300,11 @@ class customCheckButton(object): def is_active(self): return self.chk.get_active() + def _show_info(self, widget=None, data=None): + """Show a messagebox containing info about the access right in + question""" + print self.helptext + class PinpadGTK: """This a simple GTK based GUI to enter a PIN/PUK/CAN""" @@ -297,23 +323,23 @@ class PinpadGTK: self.secret_len = 10 else: raise ValueError("Unknwon secret type: %s" % self.secret) - gtk.main_quit() + #gtk.main_quit() self.chat = chat self.cert = cert #Set the Glade file - self.gladefile = glade_dir + "/pinpad.glade" + self.gladefile = gui_globals.GLADE_FILE self.builder = gtk.Builder() try: self.builder.add_from_file(self.gladefile) - except glib.GError, e: + except glib.GError, err: #If we encounter an exception at startup, we display a popup with #the error message popup = gtk.MessageDialog(None, gtk.DIALOG_MODAL, - gtk.MESSAGE_WARNING, gtk.BUTTONS_OK, e.message) + gtk.MESSAGE_WARNING, gtk.BUTTONS_OK, err.message) popup.run() popup.destroy() - raise e + raise err #Get the Main Window, and connect the "destroy" event #Create our dictionay and connect it @@ -344,10 +370,11 @@ class PinpadGTK: lbl_cardStatus = self.builder.get_object("lbl_cardStatus") #Fetch the status image img_cardStatus = self.builder.get_object("img_cardStatus") - img_cardStatus.set_from_file(image_dir + "/error.png") + img_cardStatus.set_from_file(IMAGES["error"]) - self.cardChecker = cardChecker(lbl_cardStatus, img_cardStatus, ePA_ATR) - gobject.idle_add(self.cardChecker.start) + #We only start the thread for polling the card when the window + #is shown + self.cardChecker = None #Change the font for the buttons #For this you have to retrieve the label of each button and change @@ -372,6 +399,11 @@ class PinpadGTK: self.window.hide() def show(self): + """Start the polling thread, then show the window""" + if self.cardChecker is None: + self.cardChecker = cardChecker(lbl_cardStatus, img_cardStatus, + gui_globals.ePA_ATR) + gobject.idle_add(self.cardChecker.start) self.window.show_all() def set_chat(self, chat): @@ -379,8 +411,9 @@ class PinpadGTK: def shutdown(self, widget): """Stop the cardChecker thread before exiting the application""" - self.cardChecker.stop() - self.cardChecker.join() + if self.cardChecker is not None: + self.cardChecker.stop() + self.cardChecker.join() gtk.main_quit() def digit_clicked(self, widget): @@ -442,9 +475,9 @@ class PinpadGTK: try: #Stop polling the card while PACE is running self.cardChecker.pause() - p = subprocess.Popen(cmd, stdout=subprocess.PIPE, env=env_args, + proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, env=env_args, close_fds=True) - except OSError, e: + except OSError: popup = gtk.MessageDialog(self.window, gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT, gtk.MESSAGE_ERROR, gtk.BUTTONS_OK, "pace-tool wurde nicht gefunden.") @@ -455,7 +488,7 @@ class PinpadGTK: #Show the animation to indicate that the program is not dead waiting = gtk.Window(gtk.WINDOW_POPUP) - animation = gtk.gdk.PixbufAnimation(image_dir + "/wait.gif") + animation = gtk.gdk.PixbufAnimation(IMAGES["wait"]) img = gtk.Image() img.set_from_animation(animation) waiting.add(img) @@ -466,14 +499,14 @@ class PinpadGTK: waiting.show_all() #Try to keep the GUI responsive by taking care of the event queue - line = p.stdout.readline() + line = proc.stdout.readline() while line: while gtk.events_pending(): gtk.main_iteration() - line = p.stdout.readline() + line = proc.stdout.readline() #Get the return value of the pace-tool process - ret = p.poll() + ret = proc.poll() waiting.destroy() self.cardChecker.resume() @@ -514,10 +547,10 @@ class cardChecker(Thread): self._paused = Event() self.lbl = lbl self.img = img - self.targetATR = atr + self.target_atr = atr self.intervall = intervall - def __cardCheck(self): + def _check_card(self): """ Actually this method should make use of pyscard, but I couldn't make it run on the OpenMoko yet. Therefor we call opensc-tool instead, which @@ -527,12 +560,12 @@ class cardChecker(Thread): stdout=subprocess.PIPE, stderr=subprocess.PIPE, close_fds=True) line = proc.stdout.readline().rstrip() - if line == self.targetATR: + if line == self.target_atr: gobject.idle_add(self.lbl.set_label, STR_CARD_FOUND) - gobject.idle_add(self.img.set_from_file, FILE_CARD_FOUND) + gobject.idle_add(self.img.set_from_file, IMAGES["apply"]) else: gobject.idle_add(self.lbl.set_label, STR_NO_CARD) - gobject.idle_add(self.img.set_from_file, FILE_NO_CARD) + gobject.idle_add(self.img.set_from_file, IMAGES["error"]) def run(self): """Main loop: Poll the card if the thread is not paused or finished""" @@ -541,7 +574,7 @@ class cardChecker(Thread): return if self._paused.isSet(): continue - self.__cardCheck() + self._check_card() self._finished.wait(self.intervall) def stop(self): @@ -555,5 +588,6 @@ class cardChecker(Thread): if __name__ == "__main__": gobject.threads_init() - CertificateDescriptionWindow(TEST_DESCRIPTION, TEST_CVC) + CertificateDescriptionWindow(gui_globals.TEST_DESCRIPTION, + gui_globals.TEST_CVC) gtk.main() diff --git a/pace-gui/src/pace_gui_globals.py.in b/pace-gui/src/pace_gui_globals.py.in index 3dd6dff..55258ec 100644 --- a/pace-gui/src/pace_gui_globals.py.in +++ b/pace-gui/src/pace_gui_globals.py.in @@ -5,6 +5,8 @@ version = "@VERSION@" image_dir = "@prefix@/share/" + name glade_dir = "@prefix@/share/" + name +GLADE_FILE = glade_dir + "/pinpad.glade" + STR_NO_CARD = "Ausweis" STR_CARD_FOUND = "Ausweis" @@ -13,46 +15,76 @@ FILE_CARD_FOUND = image_dir + "/apply.png" ePA_ATR = "3b:84:80:01:00:00:90:00:95" -AT_CHAT_STRINGS = [ - "Altersverifikation", #Age Verification - u"Gemeindeschlüssel Verifikation", #Community ID Verification - "Pseudonymfunktion", #Restrictied Identification - "Priviligiertes Terminal", #Privileged Terminal - "Verwendung der CAN", #CAN allowed - "PIN Managment", - "Zertifikat einspielen", #Install Certificate - "Qualifiziertes Zertifikat einspielen", #Install Qualified Certificate - "Dokumenttyp lesen", #Read DG 1 - "Staat lesen", #Read DG 2 - "Ablaufdatum lesen", #Read DG 3 - "Vorname lesen", #Read DG 4 - "Nachname lesen", #Read DG 5 - u"Künstlername lesen", #Read DG 6 - "Doktorgrad lesen", #Read DG 7 - "Geburtsdatum lesen", #Read DG 8 - "Geburtsort lesen", #Read DG 9 - u"Ungültig", #Read DG 10 - u"Ungültig", #Read DG 11 - u"Ungültig", #Read DG 12 - u"Ungültig", #Read DG 13 - u"Ungültig", #Read DG 14 - u"Ungültig", #Read DG 15 - u"Ungültig", #Read DG 16 - "Adresse lesen", #Read DG 17 - u"Gemeindeschlüssel lesen", #Read DG 18 - u"Ungültig", #Read DG 19 - u"Ungültig", #Read DG 20 - u"Ungültig", #Read DG 21 - u"Ungültig", #RFU - u"Ungültig", #RFU - u"Ungültig", #RFU - u"Ungültig", #RFU - u"Ungültig", #Write DG 21 - u"Ungültig", #Write DG 20 - u"Ungültig", #Write DG 19 - u"Gemeindeschlüssel schreiben", #Write DG 18 - u"Adresse schreiben", #Write DG 17" -] +#The following strings are used as help texts for eID access rights +DOC_AGE_VERIFICATION = "foo" +DOC_COMMUNITY_ID_VERIFICATION = "foo" +DOC_RESTRICTED_ID = "foo" +DOC_PRIVILEGED_TERMINAL = "foo" +DOC_CAN_ALLOWED = "foo" +DOC_PIN_MANAGMENT = "foo" +DOC_INSTALL_CERTIFICATE = "foo" +DOC_INSTALL_QC = "foo" +DOC_DOCUMENT_TYPE = "foo" +DOC_STATE = "foo" +DOC_EXPIRY_DATE = "foo" + +IMAGES = { + "apply": image_dir + "/apply.png", + "error": image_dir + "/error.png", + "info" : image_dir + "/info.png", + "wait" : image_dir + "/wait.gif" +} + +#Authentication Terminal CHAT +AT_CHAT_STRINGS = { + #Age Verification + 0: ("Altersverifikation", DOC_AGE_VERIFICATION), + #Community ID Verification + 1: (u"Gemeindeschlüssel Verifikation", DOC_COMMUNITY_ID_VERIFICATION), + #Restrictied Identification + 2: ("Pseudonymfunktion", DOC_RESTRICTED_ID), + #Privileged Terminal + 3: ("Priviligiertes Terminal", DOC_PRIVILEGED_TERMINAL), + #CAN allowed + 4: ("Verwendung der CAN", DOC_CAN_ALLOWED), + 5: ("PIN Managment", DOC_PIN_MANAGMENT), + #Install Certificate + 6: ("Zertifikat einspielen", DOC_INSTALL_CERTIFICATE), + #Install Qualified Certificate + 7: ("Qualifiziertes Zertifikat einspielen", DOC_INSTALL_QC), + #Read DG 1 + 8: ("Dokumenttyp", DOC_DOCUMENT_TYPE), + #Read DG 2 + 9: ("Ausstellender Staat", DOC_STATE), + 10: ("Ablaufdatum", DOC_EXPIRY_DATE), #Read DG 3 + 11: ("Vorname", None), #Read DG 4 + 12: ("Nachname", None), #Read DG 5 + 13: (u"Künstlername", None), #Read DG 6 + 14: ("Doktorgrad", None), #Read DG 7 + 15: ("Geburtsdatum", None), #Read DG 8 + 16: ("Geburtsort", None), #Read DG 9 + 17: (u"Ungültig", None), #Read DG 10 + 18: (u"Ungültig", None), #Read DG 11 + 19: (u"Ungültig", None),#Read DG 12 + 20: (u"Ungültig", None),#Read DG 13 + 21: (u"Ungültig", None),#Read DG 14 + 22: (u"Ungültig", None),#Read DG 15 + 23: (u"Ungültig", None),#Read DG 16 + 24: ("Adresse", None), #Read DG 17 + 25: (u"Gemeindeschlüssel lesen", None),#Read DG 18 + 26: (u"Ungültig", None),#Read DG 19 + 27: (u"Ungültig", None),#Read DG 20 + 28: (u"Ungültig", None),#Read DG 21 + 29: (u"Ungültig", None),#RFU + 30: (u"Ungültig", None),#RFU + 31: (u"Ungültig", None),#RFU + 32: (u"Ungültig", None),#RFU + 33: (u"Ungültig", None),#Write DG 21 + 34: (u"Ungültig", None),#Write DG 20 + 35: (u"Ungültig", None),#Write DG 19 + 36: (u"Gemeindeschlüssel schreiben", None),#Write DG 18 + 37: (u"Adresse schreiben", None)#Write DG 17" +} TEST_CVC = "\x7F\x21\x82\x01\x41\x7F\x4E\x81\xFA\x5F\x29\x01\x00\x42\x0D\x5A\x5A\x44\x56\x43\x41\x41\x54\x41\x30\x30\x30\x33\x7F\x49\x4F\x06\x0A\x04\x00\x7F\x00\x07\x02\x02\x02\x02\x03\x86\x41\x04\x19\xD1\x75\x45\xD3\xFE\x0B\x34\x3E\x7E\xE2\xAE\x4E\x2B\xC9\x2D\x51\x35\x1C\xC1\x17\xA4\x7F\xA9\x51\x9A\xDB\x1E\x40\x5E\xE6\xB8\x12\x12\x80\xBC\xC2\xFF\xF0\x35\x7A\x19\x7D\xE7\x39\xA7\xFD\x2E\xF0\x22\x10\xEF\x34\x3C\xDB\xE7\x9E\xF9\x4B\x8E\x28\x59\x1B\xB9\x5F\x20\x0B\x5A\x5A\x44\x4B\x42\x32\x30\x30\x30\x30\x52\x7F\x4C\x12\x06\x09\x04\x00\x7F\x00\x07\x03\x01\x02\x02\x53\x05\x00\x03\x01\xDF\x04\x5F\x25\x06\x01\x00\x00\x02\x01\x07\x5F\x24\x06\x01\x00\x00\x03\x03\x01\x65\x5E\x73\x2D\x06\x09\x04\x00\x7F\x00\x07\x03\x01\x03\x01\x80\x20\x75\xE0\xC4\xAC\x36\xC2\x5A\x33\xAC\x0E\x9A\x75\xEB\x79\x2A\x72\xF3\x31\xA5\x1E\x28\x63\x4E\xCC\x2E\xD6\x2E\x54\xF3\xC6\x93\xDA\x73\x2D\x06\x09\x04\x00\x7F\x00\x07\x03\x01\x03\x02\x80\x20\x18\x12\x65\x74\x49\xFC\xF1\xD3\xDA\xD8\x3D\x13\x14\x29\x17\x5C\x61\x8B\x21\xBA\xF0\xAF\x44\xAC\xE3\x8C\xB2\xC1\x2C\xEB\x2A\x56\x5F\x37\x40\x54\x0F\x85\x09\x12\xAB\xD3\x51\xF8\xF5\x56\x9B\x53\x4A\x5C\x8F\x64\x54\x5B\x51\xA7\x34\x70\xBE\x5A\xD2\x89\xC1\x9A\x5E\x13\x52\x53\xD3\xBB\x15\x52\x26\x21\x7B\x41\xE7\xF0\x68\xB3\x52\x3F\x3A\x63\x92\x22\xAF\x2B\x62\x8C\x39\x7D\x4F\xD4\x02\x1E\xDE\x00\xDC"