Merge pull request #35 from d0/master
Minor improvements to CardGenerator and the corresponding unit test
This commit is contained in:
@@ -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)
|
||||||
|
|
||||||
|
|||||||
@@ -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()
|
||||||
|
|||||||
Reference in New Issue
Block a user