Files
vsmartcard/pace-gui/src/pace-gui.py

531 lines
19 KiB
Python

#!/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()