Renamed pinpad.py to pace-gui.py and pinpad_globals.py to pace-gui-globals.py
git-svn-id: https://vsmartcard.svn.sourceforge.net/svnroot/vsmartcard@188 96b47cad-a561-4643-ad3b-153ac7d7599c
This commit is contained in:
530
pace-gui/src/pace-gui.py
Normal file
530
pace-gui/src/pace-gui.py
Normal file
@@ -0,0 +1,530 @@
|
||||
#!/usr/bin/env python
|
||||
# coding: utf-8
|
||||
|
||||
import sys, os, subprocess
|
||||
from threading import Thread, Event
|
||||
try:
|
||||
import pygtk
|
||||
pygtk.require("2.0")
|
||||
import gtk, gobject, glib, pango
|
||||
except:
|
||||
sys.exit(1)
|
||||
|
||||
try:
|
||||
import pace
|
||||
except:
|
||||
print >> sys.stderr, "Could not import pace module, please install pyPACE"
|
||||
sys.exit(1)
|
||||
|
||||
from pinpad_globals import *
|
||||
|
||||
at_chat_strings = [
|
||||
"Altersverifikation", #"Age Verification",
|
||||
"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"
|
||||
]
|
||||
|
||||
class MokoWindow(gtk.Window):
|
||||
"""
|
||||
Base class for all our dialogues. It's a simple gtk.Window, with a title at
|
||||
the top and to buttons at the bottom. In the middle there is a vBox where
|
||||
we can put custom stuff for each dialoge.
|
||||
All the dialoges are stored as a double-linked list, so the navigation from
|
||||
one window to another is easy
|
||||
"""
|
||||
|
||||
def __init__(self, title, predecessor):
|
||||
super(MokoWindow, self).__init__()
|
||||
|
||||
self.title_str = title
|
||||
self.predecessor = predecessor
|
||||
|
||||
assert(isinstance(self.title_str, basestring))
|
||||
|
||||
self.connect("destroy", gtk.main_quit)
|
||||
self.set_default_size(480, 640) #Display resolution of the OpenMoko
|
||||
#self.set_resizable(False)
|
||||
|
||||
#Main VBox, which consists of the title, the body, and the buttons
|
||||
#The size of the elements is inhomogenous and the spacing between elements is 5 pixel
|
||||
self.__vb = gtk.VBox(False, 5)
|
||||
self.add(self.__vb)
|
||||
|
||||
#Title label at the top of the window
|
||||
lblTitle = gtk.Label(self.title_str)
|
||||
lblTitle.modify_font(pango.FontDescription("sans 14"))
|
||||
lblTitle.set_alignment(0.5, 0.0)
|
||||
self.__vb.pack_start(lblTitle, False, False)
|
||||
|
||||
#Body VBox for custom widgets
|
||||
self.body = gtk.VBox(False)
|
||||
#Add a scrollbar in case we must display lots of information
|
||||
#Only use a vertical spacebar, not a horizontal one
|
||||
scrolled_window = gtk.ScrolledWindow()
|
||||
scrolled_window.set_policy(gtk.POLICY_NEVER, gtk.POLICY_AUTOMATIC)
|
||||
scrolled_window.set_shadow_type(gtk.SHADOW_ETCHED_OUT)
|
||||
self.__vb.pack_start(scrolled_window)
|
||||
scrolled_window.add_with_viewport(self.body)
|
||||
|
||||
#Add two buttons at the bottom of the window
|
||||
hbox = gtk.HBox(True, 5)
|
||||
btnBack = gtk.Button(u"Zurück")
|
||||
btnBack.set_size_request(150, 75)
|
||||
btnBack.connect("clicked", self.btnBack_clicked, None)
|
||||
if isinstance(btnBack.child, gtk.Label):
|
||||
lbl = btnBack.child
|
||||
else:
|
||||
raise ValueError("Button does not have a label")
|
||||
lbl.modify_font(pango.FontDescription("sans 12"))
|
||||
hbox.pack_start(btnBack, True, True)
|
||||
|
||||
btnForward = gtk.Button("Weiter")
|
||||
btnForward.set_size_request(150, 75)
|
||||
btnForward.connect("clicked", self.btnForward_clicked, None)
|
||||
if isinstance(btnForward.child, gtk.Label):
|
||||
lbl = btnForward.child
|
||||
else:
|
||||
raise ValueError("Button does not have a label")
|
||||
hbox.pack_start(btnForward, True, True)
|
||||
lbl.modify_font(pango.FontDescription("sans 12"))
|
||||
self.__vb.pack_start(hbox, False, False)
|
||||
|
||||
def btnBack_clicked(self, widget, data=None):
|
||||
if self.predecessor == None:
|
||||
gtk.main_quit()
|
||||
else:
|
||||
self.hide()
|
||||
self.predecessor.show()
|
||||
# raise NotImplementedError("Please implement this method in a subclass")
|
||||
|
||||
def btnForward_clicked(self, widget, data=None):
|
||||
if self.successor == None:
|
||||
pass
|
||||
else:
|
||||
self.hide()
|
||||
self.successor.show_all()
|
||||
# raise NotImplementedError("Please implement this method in a subclass")
|
||||
|
||||
class CertificateDescriptionWindow(MokoWindow):
|
||||
|
||||
def __init__(self, binDescription, binCert):
|
||||
super(CertificateDescriptionWindow, self).__init__("Dienstanbieter", None)
|
||||
|
||||
#binDesciption contains the Certificate Description as a octet string
|
||||
desc = pace.d2i_CVC_CERTIFICATE_DESCRIPTION(binDescription)
|
||||
|
||||
self.successor = CVCWindow(binCert, self)
|
||||
|
||||
#Display the validity period:
|
||||
cvc = pace.d2i_CV_CERT(binCert) #FIXME
|
||||
effective_date = self.formatDate(pace.get_effective_date(cvc))
|
||||
expiration_date = self.formatDate(pace.get_expiration_date(cvc))
|
||||
self.addRow(u"Gültig ab:", effective_date)
|
||||
self.addRow(u"Gültig bis:", expiration_date)
|
||||
|
||||
#Display issuer Name and possibly URL and subject name and possibly URL
|
||||
issuerName = pace.get_issuer_name(desc)
|
||||
issuerURL = pace.get_issuer_url(desc)
|
||||
self.addRow("Zertifikatsaussteller:", issuerName)
|
||||
if issuerURL != None:
|
||||
self.addRow("", issuerURL, True)
|
||||
subjectName = pace.get_subject_name(desc)
|
||||
subjectURL = pace.get_subject_url(desc)
|
||||
self.addRow("Zertifikatsinhaber:", subjectName)
|
||||
if subjectURL != None:
|
||||
self.addRow("", subjectURL, True)
|
||||
self.set_focus(None)
|
||||
|
||||
#Extract, format and display the terms of usage
|
||||
terms = pace.get_termsOfUsage(desc)
|
||||
formated_terms = self.formatTOU(terms)
|
||||
self.addRow("Beschreibung:", formated_terms)
|
||||
|
||||
self.show_all()
|
||||
|
||||
def formatDate(self, date):
|
||||
"""Take a date string in the form that is included in CV Certificates
|
||||
(YYMMDD) and return it in a better readable format (DD.MM.YYYY)"""
|
||||
|
||||
assert(isinstance(date, basestring))
|
||||
assert(len(date) == 6)
|
||||
|
||||
yy = date[:2]
|
||||
mm = date[2:4]
|
||||
dd = date[4:6] #Cut off trailing \0
|
||||
|
||||
#Implicit assumption: 2000 <= year < 3000
|
||||
return dd + "." + mm + "." + "20" + yy
|
||||
|
||||
def formatTOU(self, terms):
|
||||
|
||||
# for s in self.terms_array:
|
||||
# s = s.replace(", ", "\n").strip()
|
||||
# lbl = gtk.Label(s)
|
||||
# lbl.set_alignment(0.0, 0.0)
|
||||
# lbl.set_width_chars(60)
|
||||
# lbl.set_line_wrap(True) #FIXME: Doesn't work on the Moko
|
||||
# lbl.modify_font(pango.FontDescription("bold"))
|
||||
# self.body.pack_start(gtk.HSeparator())
|
||||
# self.body.pack_start(lbl, False, False, 2)
|
||||
|
||||
return terms.replace(", ", "\r\n")
|
||||
|
||||
def addRow(self, title, text, url=False):
|
||||
if not url: #URL has no title
|
||||
lblTitle = gtk.Label(title)
|
||||
lblTitle.set_width_chars(20)
|
||||
lblTitle.set_alignment(0.0, 0.5)
|
||||
lblTitle.modify_font(pango.FontDescription("bold"))
|
||||
self.body.pack_start(lblTitle)
|
||||
hbox = gtk.HBox()
|
||||
lbl = gtk.Label("") #Empty label for spacing
|
||||
lbl.set_width_chars(3)
|
||||
hbox.pack_start(lbl)
|
||||
if url:
|
||||
lblText = gtk.LinkButton(text, text)
|
||||
if isinstance(lblText.child, gtk.Label):
|
||||
lbl = lblText.child
|
||||
else:
|
||||
raise ValueError("Button does not have a label")
|
||||
lbl.set_width_chars(40)
|
||||
|
||||
else:
|
||||
lblText = gtk.Label(text)
|
||||
lblText.set_width_chars(40)
|
||||
|
||||
lblText.set_alignment(0.0, 0.0)
|
||||
hbox.pack_start(lblText)
|
||||
self.body.pack_start(hbox)
|
||||
|
||||
class CVCWindow(MokoWindow):
|
||||
|
||||
def __init__(self, binCert, predecessor):
|
||||
super(CVCWindow, self).__init__("Zugriffsrechte", None)
|
||||
|
||||
self.predecessor = predecessor
|
||||
# self.successor = PinpadWindow(self)
|
||||
|
||||
#Convert the binary certificate to the internal representation and
|
||||
#extract the relative authorization from the chat
|
||||
cvc = pace.d2i_CV_CERT(binCert)
|
||||
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))
|
||||
self.rel_auth_len = len(self.rel_auth)
|
||||
|
||||
#Extract the access rights from the CHAT and display them in the window
|
||||
self.access_rights = []
|
||||
j = 0
|
||||
for i in range((self.rel_auth_len - 1) * 8 - 2):
|
||||
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)
|
||||
self.access_rights.append(chk)
|
||||
|
||||
# self.show_all()
|
||||
|
||||
def btnForward_clicked(self, widget, data=None):
|
||||
""" Check wether any access right have been deselected and modify the
|
||||
CHAT accordingly """
|
||||
|
||||
for right in self.access_rights:
|
||||
if not right.is_active():
|
||||
idx = right.idx
|
||||
self.chat_array[len(self.chat) - 1 - idx / 8] ^= (1 << (idx % 8))
|
||||
|
||||
#super(CVCWindow, self).btnForward_clicked(widget, data)
|
||||
self.hide()
|
||||
PinpadGTK()
|
||||
|
||||
class customCheckButton(object):
|
||||
"""This class provides a custom version of gtk.CheckButton.
|
||||
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):
|
||||
#Setup a label with the name of the access right
|
||||
self.lbl = gtk.Label(label)
|
||||
self.lbl.set_alignment(0.0, 0.5)
|
||||
self.lbl.set_padding(20, 0)
|
||||
self.lbl.modify_font(pango.FontDescription("bold"))
|
||||
|
||||
#...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.lbl, True, True)
|
||||
hbox.pack_start(self.chk, False, False)
|
||||
vbox.pack_start(gtk.HSeparator())
|
||||
vbox.pack_start(hbox)
|
||||
|
||||
def is_active(self):
|
||||
return self.chk.get_active()
|
||||
|
||||
class PinpadGTK:
|
||||
"""This a simple GTK based GUI to enter a PIN"""
|
||||
|
||||
def __init__(self):
|
||||
btn_names = ["btnOne", "btnTwo", "btnThree", "btnFour", "btnFive",
|
||||
"btnSix", "btnSeven", "btnEight", "btnNine", "btnDel",
|
||||
"btnZero", "btnOk"]
|
||||
|
||||
self.pin = ""
|
||||
|
||||
#Set the Glade file
|
||||
self.gladefile = glade_dir + "/pinpad.glade"
|
||||
self.builder = gtk.Builder()
|
||||
try:
|
||||
self.builder.add_from_file(self.gladefile)
|
||||
except glib.GError, e:
|
||||
#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)
|
||||
popup.run()
|
||||
popup.destroy()
|
||||
raise e
|
||||
|
||||
#Get the Main Window, and connect the "destroy" event
|
||||
#Create our dictionay and connect it
|
||||
dic = { "on_btnOne_clicked" : self.digit_clicked,
|
||||
"on_btnTwo_clicked" : self.digit_clicked,
|
||||
"on_btnThree_clicked" : self.digit_clicked,
|
||||
"on_btnFour_clicked" : self.digit_clicked,
|
||||
"on_btnFive_clicked" : self.digit_clicked,
|
||||
"on_btnSix_clicked" : self.digit_clicked,
|
||||
"on_btnSeven_clicked" : self.digit_clicked,
|
||||
"on_btnEight_clicked" : self.digit_clicked,
|
||||
"on_btnNine_clicked" : self.digit_clicked,
|
||||
"on_btnZero_clicked" : self.digit_clicked,
|
||||
"on_btnOk_clicked" : self.btnOk_clicked,
|
||||
"on_btnDel_clicked" : self.btnDel_clicked,
|
||||
"on_MainWindow_destroy_event" : gtk.main_quit,
|
||||
"on_MainWindow_destroy" : gtk.main_quit }
|
||||
|
||||
self.builder.connect_signals(dic)
|
||||
|
||||
#Change the font for the text field
|
||||
txtOutput = self.builder.get_object("txtOutput")
|
||||
txtOutput.modify_font(pango.FontDescription("Monospace 16"))
|
||||
|
||||
#Look for card and set the label accordingly
|
||||
lbl_cardStatus = self.builder.get_object("lbl_cardStatus")
|
||||
self.cardChecker = cardChecker(lbl_cardStatus, ePA_ATR)
|
||||
gobject.idle_add(self.cardChecker.start)
|
||||
|
||||
#Change the font for the buttons
|
||||
#For this you have to retrieve the label of each button and change
|
||||
#its font
|
||||
for name in btn_names:
|
||||
btn = self.builder.get_object(name)
|
||||
if btn.get_use_stock():
|
||||
lbl = btn.child.get_children()[1]
|
||||
elif isinstance(btn.child, gtk.Label):
|
||||
lbl = btn.child
|
||||
else:
|
||||
raise ValueError("Button does not have a label")
|
||||
|
||||
if (name == "btnOk" or name == "btnDel"):
|
||||
lbl.modify_font(pango.FontDescription("sans 14"))
|
||||
else:
|
||||
lbl.modify_font(pango.FontDescription("sans 18"))
|
||||
|
||||
#Prepare the Progress Bar window for later use
|
||||
self.progressWindow = Popup(self.builder)
|
||||
|
||||
#Display Main Window
|
||||
self.window = self.builder.get_object("MainWindow")
|
||||
self.window.show()
|
||||
|
||||
if (self.window):
|
||||
self.window.connect("destroy", self.shutdown)
|
||||
|
||||
def shutdown(self, widget):
|
||||
self.cardChecker.stop()
|
||||
self.cardChecker.join()
|
||||
gtk.main_quit()
|
||||
|
||||
def digit_clicked(self, widget):
|
||||
c = widget.get_label()
|
||||
lbl = self.builder.get_object("txtOutput")
|
||||
txt = lbl.get_text()
|
||||
if len(txt) < 6:
|
||||
lbl.set_text(txt + '*')
|
||||
self.pin += widget.get_label()
|
||||
|
||||
def btnOk_clicked(self, widget):
|
||||
|
||||
#Check which radio button is checked, so we know what kind of secret to
|
||||
#use for PACE
|
||||
env_arg = os.environ
|
||||
env_arg["PIN"] = self.pin #We alway use the eID-PIN
|
||||
|
||||
#We have to provide the type of secret to use via a command line parameter
|
||||
param = "--pin"
|
||||
|
||||
try:
|
||||
self.cardChecker.pause() #Stop polling the card while PACE is running
|
||||
p = subprocess.Popen(["pace-tool", param, "-v"],
|
||||
stdout=subprocess.PIPE, env=env_arg, close_fds=True)
|
||||
except OSError, e:
|
||||
popup = gtk.MessageDialog(self.window, gtk.DIALOG_MODAL |
|
||||
gtk.DIALOG_DESTROY_WITH_PARENT, gtk.MESSAGE_ERROR,
|
||||
gtk.BUTTONS_OK, "pace-tool wurde nicht gefunden.")
|
||||
popup.run()
|
||||
popup.destroy()
|
||||
self.cardChecker.resume() #Restart cardChecker
|
||||
return
|
||||
|
||||
line = p.stdout.readline()
|
||||
self.progressWindow.show()
|
||||
while gtk.events_pending():
|
||||
gtk.main_iteration()
|
||||
while line:
|
||||
self.progressWindow.inc_fraction()
|
||||
while gtk.events_pending():
|
||||
gtk.main_iteration()
|
||||
line = p.stdout.readline()
|
||||
|
||||
ret = p.poll()
|
||||
self.progressWindow.hide()
|
||||
self.cardChecker.resume() #Restart cardChecker
|
||||
|
||||
if (ret == 0):
|
||||
popup = gtk.MessageDialog(self.window, gtk.DIALOG_MODAL |
|
||||
gtk.DIALOG_DESTROY_WITH_PARENT, gtk.MESSAGE_INFO,
|
||||
gtk.BUTTONS_OK, "PIN wurde korrekt eingegeben")
|
||||
res = popup.run()
|
||||
gtk.main_quit()
|
||||
else:
|
||||
lbl = self.builder.get_object("txtOutput")
|
||||
popup = gtk.MessageDialog(self.window, gtk.DIALOG_MODAL |
|
||||
gtk.DIALOG_DESTROY_WITH_PARENT, gtk.MESSAGE_WARNING,
|
||||
gtk.BUTTONS_OK,
|
||||
"PIN wurde falsch eingegeben. Bitte erneut versuchen")
|
||||
res = popup.run()
|
||||
popup.destroy()
|
||||
lbl.set_text("")
|
||||
self.pin = ""
|
||||
|
||||
def btnDel_clicked(self, widget):
|
||||
self.pin = self.pin[:-1]
|
||||
lbl = self.builder.get_object("txtOutput")
|
||||
txt = lbl.get_text()
|
||||
if len(txt) < 6:
|
||||
lbl.set_text(len(self.pin) * '*')
|
||||
|
||||
|
||||
class Popup(object):
|
||||
|
||||
def __init__(self, builder):
|
||||
self.builder = builder
|
||||
self.window = self.builder.get_object("progWindow")
|
||||
self.progbar = self.builder.get_object("progBar")
|
||||
|
||||
def show(self):
|
||||
self.window.show()
|
||||
|
||||
def hide(self):
|
||||
self.window.hide()
|
||||
|
||||
def inc_fraction(self):
|
||||
fraction = self.progbar.get_fraction()
|
||||
if fraction < 1.0:
|
||||
fraction += 0.1
|
||||
else:
|
||||
fraction = 0.0
|
||||
self.progbar.set_fraction(fraction)
|
||||
|
||||
class cardChecker(Thread):
|
||||
""" This class searches for a card with a given ATR and displays
|
||||
wether or not it was found on the label of a widget """
|
||||
|
||||
def __init__(self, widget, atr, intervall=1):
|
||||
Thread.__init__(self)
|
||||
self._finished = Event()
|
||||
self._paused = Event()
|
||||
self.widget = widget
|
||||
self.targetATR = atr
|
||||
self.intervall = intervall
|
||||
|
||||
def __cardCheck(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
|
||||
leads to higher delays """
|
||||
|
||||
try:
|
||||
p = subprocess.Popen(["opensc-tool", "--atr"], stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE, close_fds=True)
|
||||
|
||||
line = p.stdout.readline().rstrip()
|
||||
if line == self.targetATR:
|
||||
gobject.idle_add(self.widget.set_label, STR_CARD_FOUND)
|
||||
else:
|
||||
gobject.idle_add(self.widget.set_label, STR_NO_CARD)
|
||||
|
||||
except OSError, e:
|
||||
pass #FIXME
|
||||
|
||||
def run(self):
|
||||
while (True):
|
||||
if self._finished.isSet(): return
|
||||
if self._paused.isSet(): continue
|
||||
self.__cardCheck()
|
||||
self._finished.wait(self.intervall)
|
||||
|
||||
def stop(self):
|
||||
self._finished.set()
|
||||
|
||||
def pause(self):
|
||||
self._paused.set()
|
||||
|
||||
def resume(self):
|
||||
self._paused.clear()
|
||||
|
||||
if __name__ == "__main__":
|
||||
CertificateDescriptionWindow(TEST_DESCRIPTION, TEST_CVC)
|
||||
gtk.main()
|
||||
Reference in New Issue
Block a user