Merge pull request #35 from d0/master

Minor improvements to CardGenerator and the corresponding unit test
This commit is contained in:
Frank Morgner
2015-01-08 12:12:02 +01:00
2 changed files with 65 additions and 58 deletions

View File

@@ -1,18 +1,18 @@
# #
# Copyright (C) 2011 Dominik Oepen # Copyright (C) 2011 Dominik Oepen
# #
# This file is part of virtualsmartcard. # This file is part of virtualsmartcard.
# #
# virtualsmartcard is free software: you can redistribute it and/or modify it # virtualsmartcard 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 # under the terms of the GNU General Public License as published by the Free
# Software Foundation, either version 3 of the License, or (at your option) any # Software Foundation, either version 3 of the License, or (at your option) any
# later version. # later version.
# #
# virtualsmartcard is distributed in the hope that it will be useful, but # virtualsmartcard is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or # WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for # FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
# more details. # more details.
# #
# You should have received a copy of the GNU General Public License along with # You should have received a copy of the GNU General Public License along with
# virtualsmartcard. If not, see <http://www.gnu.org/licenses/>. # virtualsmartcard. If not, see <http://www.gnu.org/licenses/>.
# #
@@ -39,52 +39,52 @@ except ImportError:
#self.mf.append(DF(parent=self.mf, #self.mf.append(DF(parent=self.mf,
#fid=2, dfname='\xa0\x00\x00\x03\x08\x00\x00\x10\x00')) #fid=2, dfname='\xa0\x00\x00\x03\x08\x00\x00\x10\x00'))
#self.mf.append(DF(parent=self.mf, #self.mf.append(DF(parent=self.mf,
#fid=3, dfname='\xa0\x00\x00\x03\x08\x00\x00\x10\x00\x01\x00')) #fid=3, dfname='\xa0\x00\x00\x03\x08\x00\x00\x10\x00\x01\x00'))
class CardGenerator(object): class CardGenerator(object):
"""This class is used to generate the SAM and filesystem for the """This class is used to generate the SAM and filesystem for the
different supported card types. It is also able used for persistent storage different supported card types. It is also able used for persistent storage
(in encrypted form) of the card on disks. """ (in encrypted form) of the card on disks. """
def __init__(self, card_type=None, sam=None, mf=None): def __init__(self, card_type=None, sam=None, mf=None):
self.type = card_type self.type = card_type
self.mf = mf self.mf = mf
self.sam = sam self.sam = sam
self.password = None self.password = None
self.datagroups = {} self.datagroups = {}
def __generate_iso_card(self): def __generate_iso_card(self):
default_pin = "1234" default_pin = "1234"
default_cardno = "1234567890" default_cardno = "1234567890"
logging.warning("Using default SAM parameters. PIN=%s, Card Nr=%s" logging.warning("Using default SAM parameters. PIN=%s, Card Nr=%s"
% (default_pin, default_cardno)) % (default_pin, default_cardno))
#TODO: Use user provided data #TODO: Use user provided data
self.sam = SAM(default_pin, default_cardno) self.sam = SAM(default_pin, default_cardno)
self.mf = MF(filedescriptor=FDB["DF"]) self.mf = MF(filedescriptor=FDB["DF"])
self.sam.set_MF(self.mf) self.sam.set_MF(self.mf)
def __generate_ePass(self): def __generate_ePass(self):
"""Generate the MF and SAM of an ICAO passport. This method is """Generate the MF and SAM of an ICAO passport. This method is
responsible for generating the filesystem and filling it with content. responsible for generating the filesystem and filling it with content.
Therefore it must interact with the user by prompting for the MRZ and Therefore it must interact with the user by prompting for the MRZ and
optionally for the path to a photo.""" optionally for the path to a photo."""
from PIL import Image from PIL import Image
from virtualsmartcard.cards.ePass import PassportSAM from virtualsmartcard.cards.ePass import PassportSAM
#TODO: Sanity checks #TODO: Sanity checks
MRZ = raw_input("Please enter the MRZ as one string: ") MRZ = raw_input("Please enter the MRZ as one string: ")
readline.set_completer_delims("") readline.set_completer_delims("")
readline.parse_and_bind("tab: complete") readline.parse_and_bind("tab: complete")
picturepath = raw_input("Please enter the path to an image: ") picturepath = raw_input("Please enter the path to an image: ")
picturepath = picturepath.strip() picturepath = picturepath.strip()
#MRZ1 = "P<UTOERIKSSON<<ANNA<MARIX<<<<<<<<<<<<<<<<<<<" #MRZ1 = "P<UTOERIKSSON<<ANNA<MARIX<<<<<<<<<<<<<<<<<<<"
#MRZ2 = "L898902C<3UTO6908061F9406236ZE184226B<<<<<14" #MRZ2 = "L898902C<3UTO6908061F9406236ZE184226B<<<<<14"
#MRZ = MRZ1 + MRZ2 #MRZ = MRZ1 + MRZ2
try: try:
im = Image.open(picturepath) im = Image.open(picturepath)
@@ -96,10 +96,10 @@ class CardGenerator(object):
logging.warning("Failed to open file: " + picturepath) logging.warning("Failed to open file: " + picturepath)
pic_width = 0 pic_width = 0
pic_height = 0 pic_height = 0
picture = None picture = None
mf = MF() mf = MF()
#We need a MF with Application DF \xa0\x00\x00\x02G\x10\x01 #We need a MF with Application DF \xa0\x00\x00\x02G\x10\x01
df = DF(parent=mf, fid=4, dfname='\xa0\x00\x00\x02G\x10\x01', df = DF(parent=mf, fid=4, dfname='\xa0\x00\x00\x02G\x10\x01',
bertlv_data=[]) bertlv_data=[])
@@ -120,13 +120,13 @@ class CardGenerator(object):
#EF.DG2 #EF.DG2
if picture != None: if picture != None:
IIB = "\x00\x01" + inttostring(pic_width, 2) +\ IIB = "\x00\x01" + inttostring(pic_width, 2) +\
inttostring(pic_height, 2) + 6 * "\x00" inttostring(pic_height, 2) + 6 * "\x00"
length = 32 + len(picture) #32 is the length of IIB + FIB length = 32 + len(picture) #32 is the length of IIB + FIB
FIB = inttostring(length, 4) + 16 * "\x00" FIB = inttostring(length, 4) + 16 * "\x00"
FRH = "FAC" + "\x00" + "010" + "\x00" +\ FRH = "FAC" + "\x00" + "010" + "\x00" +\
inttostring(14 + length, 4) + inttostring(1, 2) inttostring(14 + length, 4) + inttostring(1, 2)
picture = FRH + FIB + IIB + picture picture = FRH + FIB + IIB + picture
DG2 = pack([(0xA1, 8, "\x87\x02\x01\x01\x88\x02\x05\x01"), DG2 = pack([(0xA1, 8, "\x87\x02\x01\x01\x88\x02\x05\x01"),
(0x5F2E, len(picture), picture)]) (0x5F2E, len(picture), picture)])
DG2 = pack([(0x02, 1, "\x01"), (0x7F60, len(DG2), DG2)]) DG2 = pack([(0x02, 1, "\x01"), (0x7F60, len(DG2), DG2)])
DG2 = pack([(0x7F61, len(DG2), DG2)]) DG2 = pack([(0x7F61, len(DG2), DG2)])
@@ -143,7 +143,7 @@ class CardGenerator(object):
self.mf = mf self.mf = mf
self.sam = PassportSAM(self.mf) self.sam = PassportSAM(self.mf)
def __generate_nPA(self): def __generate_nPA(self):
from virtualsmartcard.cards.nPA import nPA_SAM from virtualsmartcard.cards.nPA import nPA_SAM
for oid_base, x_list, y_list, algo in [ for oid_base, x_list, y_list, algo in [
@@ -353,7 +353,7 @@ class CardGenerator(object):
filedescriptor=0x01, filedescriptor=0x01,
data="\x00\x00\x00\x01\x00\x01\x00\x00")) #EF.ICCSN data="\x00\x00\x00\x01\x00\x01\x00\x00")) #EF.ICCSN
self.sam = CryptoflexSAM(self.mf) self.sam = CryptoflexSAM(self.mf)
def generateCard(self): def generateCard(self):
"""Generate a new card""" """Generate a new card"""
if self.type == 'iso7816': if self.type == 'iso7816':
@@ -366,34 +366,36 @@ class CardGenerator(object):
self.__generate_nPA() self.__generate_nPA()
else: else:
return (None, None) return (None, None)
def getCard(self): def getCard(self):
"""Get the MF and SAM from the current card""" """Get the MF and SAM from the current card"""
if self.sam is None or self.mf is None: if self.sam is None or self.mf is None:
self.generateCard() self.generateCard()
return self.mf, self.sam return self.mf, self.sam
def setCard(self, mf=None, sam=None): def setCard(self, mf=None, sam=None):
"""Set the MF and SAM of the current card""" """Set the MF and SAM of the current card"""
if mf != None: if mf != None:
self.mf = mf self.mf = mf
if sam != None: if sam != None:
self.sam = sam self.sam = sam
def loadCard(self, filename): def loadCard(self, filename):
"""Load a card from disk""" """Load a card from disk"""
db = anydbm.open(filename, 'r')
if self.password is None: if self.password is None:
self.password = getpass.getpass("Please enter your password:") self.password = getpass.getpass("Please enter your password:")
serializedMF = read_protected_string(db["mf"], self.password) db = anydbm.open(filename, 'r')
serializedSAM = read_protected_string(db["sam"], self.password) try:
serializedMF = read_protected_string(db["mf"], self.password)
serializedSAM = read_protected_string(db["sam"], self.password)
self.type = db["type"]
finally:
db.close()
self.sam = loads(serializedSAM) self.sam = loads(serializedSAM)
self.mf = loads(serializedMF) self.mf = loads(serializedMF)
self.type = db["type"]
def saveCard(self, filename): def saveCard(self, filename):
"""Save the currently running card to disk""" """Save the currently running card to disk"""
if self.password is None: if self.password is None:
@@ -403,35 +405,36 @@ class CardGenerator(object):
raise ValueError("Passwords did not match. Will now exit") raise ValueError("Passwords did not match. Will now exit")
else: else:
self.password = passwd1 self.password = passwd1
if self.mf == None or self.sam == None: if self.mf == None or self.sam == None:
raise ValueError("Card Generator wasn't set up properly" +\ raise ValueError("Card Generator wasn't set up properly" +\
"(missing MF or SAM).") "(missing MF or SAM).")
mf_string = dumps(self.mf) mf_string = dumps(self.mf)
sam_string = dumps(self.sam) sam_string = dumps(self.sam)
protectedMF = protect_string(mf_string, self.password) protectedMF = protect_string(mf_string, self.password)
protectedSAM = protect_string(sam_string, self.password) protectedSAM = protect_string(sam_string, self.password)
db = anydbm.open(filename, 'c') db = anydbm.open(filename, 'c')
db["mf"] = protectedMF try:
db["sam"] = protectedSAM db["mf"] = protectedMF
db["type"] = self.type db["sam"] = protectedSAM
db["version"] = "0.1" db["type"] = self.type
db.close() db["version"] = "0.1"
finally:
db.close()
def readDatagroups(self, datasetfile): def readDatagroups(self, datasetfile):
"""Read Datagroups from file""" """Read Datagroups from file"""
with open(datasetfile, 'r') as f: with open(datasetfile, 'r') as f:
for line in f: for line in f:
if (not line.startswith("#")) and (not len(line.strip()) == 0): if (not line.startswith("#")) and (not len(line.strip()) == 0):
# spaces after equal sign are allowed to get strings with leading spaces! # spaces after equal sign are allowed to get strings with leading spaces!
line=line.replace(" =", "=") line=line.replace(" =", "=")
splitLine = line.split("=") splitLine = line.split("=")
# we don't want to have the newline char from dataset file as part of the value!! # we don't want to have the newline char from dataset file as part of the value!!
self.datagroups[splitLine[0]] = splitLine[1].rstrip("\n\r") self.datagroups[splitLine[0]] = splitLine[1].rstrip("\n\r")
f.close()
if __name__ == "__main__": if __name__ == "__main__":
from optparse import OptionParser from optparse import OptionParser
parser = OptionParser() parser = OptionParser()
@@ -447,8 +450,8 @@ if __name__ == "__main__":
if options.filename == None: if options.filename == None:
logging.error("You have to provide a filename using the -f option") logging.error("You have to provide a filename using the -f option")
sys.exit() sys.exit()
generator = CardGenerator(options.type) generator = CardGenerator(options.type)
generator.generateCard() generator.generateCard()
generator.saveCard(options.filename) generator.saveCard(options.filename)

View File

@@ -17,9 +17,10 @@
# virtualsmartcard. If not, see <http://www.gnu.org/licenses/>. # virtualsmartcard. If not, see <http://www.gnu.org/licenses/>.
# #
import unittest import anydbm
import tempfile
import os import os
import tempfile
import unittest
from virtualsmartcard.CardGenerator import CardGenerator from virtualsmartcard.CardGenerator import CardGenerator
class TestNPACardGenerator(unittest.TestCase): class TestNPACardGenerator(unittest.TestCase):
@@ -29,15 +30,13 @@ class TestNPACardGenerator(unittest.TestCase):
self.nPA_generator = CardGenerator('nPA') self.nPA_generator = CardGenerator('nPA')
self.nPA_generator.password = "TestPassword" self.nPA_generator.password = "TestPassword"
def tearDown(self):
os.unlink(self.filename)
def test_nPA_creation(self): def test_nPA_creation(self):
self.nPA_generator.generateCard() self.nPA_generator.generateCard()
self.nPA_generator.saveCard(self.filename) self.nPA_generator.saveCard(self.filename)
mf, sam = self.nPA_generator.getCard() mf, sam = self.nPA_generator.getCard()
self.assertIsNotNone(mf) self.assertIsNotNone(mf)
self.assertIsNotNone(sam) self.assertIsNotNone(sam)
os.unlink(self.filename)
def test_load_nPA_from_file_nPA_from_file(self): def test_load_nPA_from_file_nPA_from_file(self):
self.nPA_generator.generateCard() self.nPA_generator.generateCard()
@@ -48,6 +47,11 @@ class TestNPACardGenerator(unittest.TestCase):
mf, sam = local_generator.getCard() mf, sam = local_generator.getCard()
self.assertIsNotNone(mf) self.assertIsNotNone(mf)
self.assertIsNotNone(sam) self.assertIsNotNone(sam)
os.unlink(self.filename)
def test_load_nonexistent_file(self):
with self.assertRaises(anydbm.error):
self.nPA_generator.loadCard(self.filename)
if __name__ == '__main__': if __name__ == '__main__':
unittest.main() unittest.main()