diff --git a/virtualsmartcard/src/vpicc/virtualsmartcard/CardGenerator.py b/virtualsmartcard/src/vpicc/virtualsmartcard/CardGenerator.py
index be2482f..cc1a7e5 100644
--- a/virtualsmartcard/src/vpicc/virtualsmartcard/CardGenerator.py
+++ b/virtualsmartcard/src/vpicc/virtualsmartcard/CardGenerator.py
@@ -17,29 +17,35 @@
# virtualsmartcard. If not, see .
#
-import sys, getpass, anydbm, logging, binascii
+import anydbm
+import binascii
+import getpass
+import logging
+try:
+ import readline
+except ImportError:
+ import pyreadline as readline
+import sys
+
from pickle import loads, dumps
-from virtualsmartcard.TLVutils import pack, unpack
+from virtualsmartcard.TLVutils import pack
from virtualsmartcard.utils import inttostring
from virtualsmartcard.SmartcardFilesystem import MF, DF, TransparentStructureEF
from virtualsmartcard.ConstantDefinitions import FDB, ALGO_MAPPING
from virtualsmartcard.CryptoUtils import protect_string, read_protected_string
from virtualsmartcard.SmartcardSAM import SAM
-try:
- import readline
-except ImportError:
- import pyreadline as readline
# pgp directory
-#self.mf.append(DF(parent=self.mf,
- #fid=4, dfname='\xd2\x76\x00\x01\x24\x01', bertlv_data=[]))
+# self.mf.append(DF(parent=self.mf,
+# fid=4, dfname='\xd2\x76\x00\x01\x24\x01', bertlv_data=[]))
# pkcs-15 directories
-#self.mf.append(DF(parent=self.mf,
- #fid=1, dfname='\xa0\x00\x00\x00\x01'))
-#self.mf.append(DF(parent=self.mf,
- #fid=2, dfname='\xa0\x00\x00\x03\x08\x00\x00\x10\x00'))
-#self.mf.append(DF(parent=self.mf,
- #fid=3, dfname='\xa0\x00\x00\x03\x08\x00\x00\x10\x00\x01\x00'))
+# self.mf.append(DF(parent=self.mf,
+# fid=1, dfname='\xa0\x00\x00\x00\x01'))
+# self.mf.append(DF(parent=self.mf,
+# fid=2, dfname='\xa0\x00\x00\x03\x08\x00\x00\x10\x00'))
+# self.mf.append(DF(parent=self.mf,
+# fid=3, dfname='\xa0\x00\x00\x03\x08\x00\x00\x10\x00\x01\x00'))
+
class CardGenerator(object):
"""This class is used to generate the SAM and filesystem for the
@@ -59,7 +65,7 @@ class CardGenerator(object):
logging.warning("Using default SAM parameters. PIN=%s, Card Nr=%s"
% (default_pin, default_cardno))
- #TODO: Use user provided data
+ # TODO: Use user provided data
self.sam = SAM(default_pin, default_cardno)
self.mf = MF(filedescriptor=FDB["DF"])
@@ -73,7 +79,7 @@ class CardGenerator(object):
from PIL import Image
from virtualsmartcard.cards.ePass import PassportSAM
- #TODO: Sanity checks
+ # TODO: Sanity checks
MRZ = raw_input("Please enter the MRZ as one string: ")
readline.set_completer_delims("")
@@ -82,14 +88,14 @@ class CardGenerator(object):
picturepath = raw_input("Please enter the path to an image: ")
picturepath = picturepath.strip()
- #MRZ1 = "P
- PlaceOfResidence = self.datagroups["PlaceOfResidence"] if "PlaceOfResidence" in self.datagroups else ''
- Country = self.datagroups["Country"] if "Country" in self.datagroups else 'D'
- City = self.datagroups["City"] if "City" in self.datagroups else 'KOLN'
+ BirthName = self.datagroups["BirthName"] if "BirthName" in \
+ self.datagroups else 'Mein Geburtsname'
+ # PlaceOfResidence variable is only a helper to get a switch for
+ #
+ PlaceOfResidence = self.datagroups["PlaceOfResidence"] if \
+ "PlaceOfResidence" in self.datagroups else ''
+ Country = self.datagroups["Country"] if "Country" in self.datagroups \
+ else 'D'
+ City = self.datagroups["City"] if "City" in self.datagroups else \
+ 'KOLN'
ZIP = self.datagroups["ZIP"] if "ZIP" in self.datagroups else '51147'
- Street = self.datagroups["Street"] if "Street" in self.datagroups else 'HEIDESTRASSE 17'
- CommunityID = self.datagroups["CommunityID"] if "CommunityID" in self.datagroups else '02760378900276'
+ Street = self.datagroups["Street"] if "Street" in \
+ self.datagroups else 'HEIDESTRASSE 17'
+ CommunityID = self.datagroups["CommunityID"] if "CommunityID" in \
+ self.datagroups else '02760378900276'
if (CommunityID.rstrip() != ""):
- # the plain CommunityID integer value has to be translated into its binary representation. '0276...' will be '\x02\x76\...'
+ # the plain CommunityID integer value has to be translated into
+ # its binary representation. '0276...' will be '\x02\x76\...'
CommunityID_Binary = binascii.unhexlify(CommunityID)
# ResidencePermit1 and ResidencePermit2 are part of eAT only
- ResidencePermit1 = self.datagroups["ResidencePermit1"] if "ResidencePermit1" in self.datagroups else 'ResidencePermit1 field up to 750 characters'
- ResidencePermit2 = self.datagroups["ResidencePermit2"] if "ResidencePermit2" in self.datagroups else 'ResidencePermit1 field up to 250 characters'
+ ResidencePermit1 = self.datagroups["ResidencePermit1"] if \
+ "ResidencePermit1" in self.datagroups else \
+ 'ResidencePermit1 field up to 750 characters'
+ ResidencePermit2 = self.datagroups["ResidencePermit2"] if \
+ "ResidencePermit2" in self.datagroups else \
+ 'ResidencePermit1 field up to 250 characters'
# Currently, those data groups are for further usage:
- dg12_param = self.datagroups["dg12"] if "dg12" in self.datagroups else ''
- dg14_param = self.datagroups["dg14"] if "dg14" in self.datagroups else ''
- dg15_param = self.datagroups["dg15"] if "dg15" in self.datagroups else ''
- dg16_param = self.datagroups["dg16"] if "dg16" in self.datagroups else ''
- dg21_param = self.datagroups["dg21"] if "dg21" in self.datagroups else ''
+ dg12_param = self.datagroups["dg12"] if "dg12" in \
+ self.datagroups else ''
+ dg14_param = self.datagroups["dg14"] if "dg14" in \
+ self.datagroups else ''
+ dg15_param = self.datagroups["dg15"] if "dg15" in \
+ self.datagroups else ''
+ dg16_param = self.datagroups["dg16"] if "dg16" in \
+ self.datagroups else ''
+ dg21_param = self.datagroups["dg21"] if "dg21" in \
+ self.datagroups else ''
- # "Attribute not on Chip" makes sence only for ReligiousArtisticName, Nationality, BirthName, ResidencePermit1 and ResidencePermit2, refer to BSI TR-03127
+ # "Attribute not on Chip" makes sence only for ReligiousArtisticName,
+ # Nationality, BirthName, ResidencePermit1 and ResidencePermit2, refer
+ # to BSI TR-03127
if (DocumentType.rstrip() != ""):
- dg1 = pack([(0x61, 0, [(0x13, 0, DocumentType)])], True)
+ dg1 = pack([(0x61, 0, [(0x13, 0, DocumentType)])], True)
else:
- dg1 = None
+ dg1 = None
if (IssuingState.rstrip() != ""):
- dg2 = pack([(0x62, 0, [(0x13, 0, IssuingState)])], True)
+ dg2 = pack([(0x62, 0, [(0x13, 0, IssuingState)])], True)
else:
- dg2 = None
+ dg2 = None
if (DateOfExpiry.rstrip() != ""):
- dg3 = pack([(0x63, 0, [(0x12, 0, DateOfExpiry)])], True)
+ dg3 = pack([(0x63, 0, [(0x12, 0, DateOfExpiry)])], True)
else:
- dg3 = None
+ dg3 = None
if (GivenNames.rstrip() != ""):
- dg4 = pack([(0x64, 0, [(0x0C, 0, GivenNames)])], True)
+ dg4 = pack([(0x64, 0, [(0x0C, 0, GivenNames)])], True)
else:
- dg4 = None
+ dg4 = None
if (FamilyNames.rstrip() != ""):
- dg5 = pack([(0x65, 0, [(0x0C, 0, FamilyNames)])], True)
+ dg5 = pack([(0x65, 0, [(0x0C, 0, FamilyNames)])], True)
else:
- dg5 = None
+ dg5 = None
if (ReligiousArtisticName.rstrip() != ""):
- dg6 = pack([(0x66, 0, [(0x0C, 0, ReligiousArtisticName)])], True)
+ dg6 = pack([(0x66, 0, [(0x0C, 0, ReligiousArtisticName)])], True)
else:
- dg6 = None
+ dg6 = None
if (AcademicTitle.rstrip() != ""):
- dg7 = pack([(0x67, 0, [(0x0C, 0, AcademicTitle)])], True)
+ dg7 = pack([(0x67, 0, [(0x0C, 0, AcademicTitle)])], True)
else:
- dg7 = None
+ dg7 = None
if (DateOfBirth.rstrip() != ""):
- dg8 = pack([(0x68, 0, [(0x12, 0, DateOfBirth)])], True)
+ dg8 = pack([(0x68, 0, [(0x12, 0, DateOfBirth)])], True)
else:
- dg8 = None
+ dg8 = None
if (PlaceOfBirth.rstrip() != ""):
- dg9 = pack([(0x69, 0, [(0xA1, 0, [(0x0C, 0, PlaceOfBirth)])])], True)
+ dg9 = pack([(0x69, 0, [(0xA1, 0, [(0x0C, 0, PlaceOfBirth)])])],
+ True)
else:
- dg9 = None
+ dg9 = None
if (Nationality.rstrip() != ""):
- dg10 = pack([(0x6A, 0, [(0x13, 0, Nationality)])], True)
+ dg10 = pack([(0x6A, 0, [(0x13, 0, Nationality)])], True)
else:
- dg10 = None
+ dg10 = None
if (Sex.rstrip() != ""):
- dg11 = pack([(0x6B, 0, [(0x13, 0, Sex)])], True)
+ dg11 = pack([(0x6B, 0, [(0x13, 0, Sex)])], True)
else:
- dg11 = None
+ dg11 = None
if (dg12_param.rstrip() != ""):
- dg12 = dg12_param
+ dg12 = dg12_param
else:
- dg12 = None
+ dg12 = None
if (BirthName.rstrip() != ""):
- dg13 = pack([(0x6D, 0, [(0x0C, 0, BirthName)])], True)
+ dg13 = pack([(0x6D, 0, [(0x0C, 0, BirthName)])], True)
else:
- dg13 = None
+ dg13 = None
if (dg14_param.rstrip() != ""):
- dg14 = dg14_param
+ dg14 = dg14_param
else:
- dg14 = None
+ dg14 = None
if (dg15_param.rstrip() != ""):
- dg15 = dg15_param
+ dg15 = dg15_param
else:
- dg15 = None
+ dg15 = None
if (dg16_param.rstrip() != ""):
- dg16 = dg16_param
+ dg16 = dg16_param
else:
- dg16 = None
+ dg16 = None
if (PlaceOfResidence.rstrip() != ""):
- dg17 = pack([(0x71, 0, [(0x30, 0, [
- (0xAA, 0, [(0x0C, 0, Street)]),
- (0xAB, 0, [(0x0C, 0, City)]),
- (0xAD, 0, [(0x13, 0, Country)]),
- (0xAE, 0, [(0x13, 0, ZIP)])
- ])])], True)
+ dg17 = pack([(0x71, 0, [(0x30, 0, [
+ (0xAA, 0, [(0x0C, 0, Street)]),
+ (0xAB, 0, [(0x0C, 0, City)]),
+ (0xAD, 0, [(0x13, 0, Country)]),
+ (0xAE, 0, [(0x13, 0, ZIP)])
+ ])])], True)
else:
- dg17 = None
+ dg17 = None
if (CommunityID.rstrip() != ""):
- dg18 = pack([(0x72, 0, [(0x04, 0, CommunityID_Binary)])], True)
+ dg18 = pack([(0x72, 0, [(0x04, 0, CommunityID_Binary)])], True)
else:
- dg18 = None
+ dg18 = None
if (ResidencePermit1.rstrip() != ""):
- dg19 = pack([(0x73, 0, [(0xA1, 0, [(0x0C, 0, ResidencePermit1)])])], True)
+ dg19 = pack([(0x73, 0, [(0xA1, 0,
+ [(0x0C, 0, ResidencePermit1)])])], True)
else:
- dg19 = None
+ dg19 = None
if (ResidencePermit1.rstrip() != ""):
- dg20 = pack([(0x74, 0, [(0xA1, 0, [(0x0C, 0, ResidencePermit2)])])], True)
+ dg20 = pack([(0x74, 0, [(0xA1, 0,
+ [(0x0C, 0, ResidencePermit2)])])], True)
else:
- dg20 = None
+ dg20 = None
if (dg21_param.rstrip() != ""):
- dg21 = dg21_param
+ dg21 = dg21_param
else:
- dg21 = None
+ dg21 = None
- # If eid.append is not done for a DG, it results into required SwError() with FileNotFound "6A82" APDU return code
+ # If eid.append is not done for a DG, it results into required
+ # SwError() with FileNotFound "6A82" APDU return code
if dg1:
- eid.append(TransparentStructureEF(parent=eid, fid=0x0101, shortfid=0x01, data=dg1))
+ eid.append(TransparentStructureEF(parent=eid, fid=0x0101,
+ shortfid=0x01, data=dg1))
if dg2:
- eid.append(TransparentStructureEF(parent=eid, fid=0x0102, shortfid=0x02, data=dg2))
+ eid.append(TransparentStructureEF(parent=eid, fid=0x0102,
+ shortfid=0x02, data=dg2))
if dg3:
- eid.append(TransparentStructureEF(parent=eid, fid=0x0103, shortfid=0x03, data=dg3))
+ eid.append(TransparentStructureEF(parent=eid, fid=0x0103,
+ shortfid=0x03, data=dg3))
if dg4:
- eid.append(TransparentStructureEF(parent=eid, fid=0x0104, shortfid=0x04, data=dg4))
+ eid.append(TransparentStructureEF(parent=eid, fid=0x0104,
+ shortfid=0x04, data=dg4))
if dg5:
- eid.append(TransparentStructureEF(parent=eid, fid=0x0105, shortfid=0x05, data=dg5))
+ eid.append(TransparentStructureEF(parent=eid, fid=0x0105,
+ shortfid=0x05, data=dg5))
if dg6:
- eid.append(TransparentStructureEF(parent=eid, fid=0x0106, shortfid=0x06, data=dg6))
+ eid.append(TransparentStructureEF(parent=eid, fid=0x0106,
+ shortfid=0x06, data=dg6))
if dg7:
- eid.append(TransparentStructureEF(parent=eid, fid=0x0107, shortfid=0x07, data=dg7))
+ eid.append(TransparentStructureEF(parent=eid, fid=0x0107,
+ shortfid=0x07, data=dg7))
if dg8:
- eid.append(TransparentStructureEF(parent=eid, fid=0x0108, shortfid=0x08, data=dg8))
+ eid.append(TransparentStructureEF(parent=eid, fid=0x0108,
+ shortfid=0x08, data=dg8))
if dg9:
- eid.append(TransparentStructureEF(parent=eid, fid=0x0109, shortfid=0x09, data=dg9))
+ eid.append(TransparentStructureEF(parent=eid, fid=0x0109,
+ shortfid=0x09, data=dg9))
if dg10:
- eid.append(TransparentStructureEF(parent=eid, fid=0x010a, shortfid=0x0a, data=dg10))
+ eid.append(TransparentStructureEF(parent=eid, fid=0x010a,
+ shortfid=0x0a, data=dg10))
if dg11:
- eid.append(TransparentStructureEF(parent=eid, fid=0x010b, shortfid=0x0b, data=dg11))
+ eid.append(TransparentStructureEF(parent=eid, fid=0x010b,
+ shortfid=0x0b, data=dg11))
if dg12:
- eid.append(TransparentStructureEF(parent=eid, fid=0x010c, shortfid=0x0c, data=dg12))
+ eid.append(TransparentStructureEF(parent=eid, fid=0x010c,
+ shortfid=0x0c, data=dg12))
if dg13:
- eid.append(TransparentStructureEF(parent=eid, fid=0x010d, shortfid=0x0d, data=dg13))
+ eid.append(TransparentStructureEF(parent=eid, fid=0x010d,
+ shortfid=0x0d, data=dg13))
if dg14:
- eid.append(TransparentStructureEF(parent=eid, fid=0x010e, shortfid=0x0e, data=dg14))
+ eid.append(TransparentStructureEF(parent=eid, fid=0x010e,
+ shortfid=0x0e, data=dg14))
if dg15:
- eid.append(TransparentStructureEF(parent=eid, fid=0x010f, shortfid=0x0f, data=dg15))
+ eid.append(TransparentStructureEF(parent=eid, fid=0x010f,
+ shortfid=0x0f, data=dg15))
if dg16:
- eid.append(TransparentStructureEF(parent=eid, fid=0x0110, shortfid=0x10, data=dg16))
+ eid.append(TransparentStructureEF(parent=eid, fid=0x0110,
+ shortfid=0x10, data=dg16))
if dg17:
- eid.append(TransparentStructureEF(parent=eid, fid=0x0111, shortfid=0x11, data=dg17))
+ eid.append(TransparentStructureEF(parent=eid, fid=0x0111,
+ shortfid=0x11, data=dg17))
if dg18:
- eid.append(TransparentStructureEF(parent=eid, fid=0x0112, shortfid=0x12, data=dg18))
+ eid.append(TransparentStructureEF(parent=eid, fid=0x0112,
+ shortfid=0x12, data=dg18))
if dg19:
- eid.append(TransparentStructureEF(parent=eid, fid=0x0113, shortfid=0x13, data=dg19))
+ eid.append(TransparentStructureEF(parent=eid, fid=0x0113,
+ shortfid=0x13, data=dg19))
if dg20:
- eid.append(TransparentStructureEF(parent=eid, fid=0x0114, shortfid=0x14, data=dg20))
+ eid.append(TransparentStructureEF(parent=eid, fid=0x0114,
+ shortfid=0x14, data=dg20))
if dg21:
- eid.append(TransparentStructureEF(parent=eid, fid=0x0115, shortfid=0x15, data=dg21))
+ eid.append(TransparentStructureEF(parent=eid, fid=0x0115,
+ shortfid=0x15, data=dg21))
self.mf.append(eid)
-
# DF.CIA
- cia=DF(parent=self.mf, fid=0xfffe, dfname='\xE8\x28\xBD\x08\x0F\xA0\x00\x00\x01\x67\x45\x53\x49\x47\x4E')
+ cia = DF(parent=self.mf, fid=0xfffe,
+ dfname='\xE8\x28\xBD\x08\x0F\xA0\x00\x00\x01\x67\x45\x53\x49'
+ '\x47\x4E')
# EF.OD / EF.ODF
- cia.append(TransparentStructureEF(parent=cia, fid=0x5031, shortfid=0x11, data='\xa0\x060\x04\x04\x02D\x00\xa4\x060\x04\x04\x02D\x04\xa8\x060\x04\x04\x02D\x08'))
+ cia.append(TransparentStructureEF(parent=cia, fid=0x5031,
+ shortfid=0x11,
+ data='\xa0\x060\x04\x04\x02D\x00\xa4'
+ '\x060\x04\x04\x02D\x04\xa8\x060\x04'
+ '\x04\x02D\x08'))
# EF.CIAInfo / EF.TokenInfo
- cia.append(TransparentStructureEF(parent=cia, fid=0x5032, shortfid=0x12, data='06\x02\x01\x01\x80\x11eSign Application\x03\x02\x06\xc0\xa2\x1a0\x18\x02\x01\x01\x02\x02\x10A\x05\x00\x03\x02\x06@\x06\t\x04\x00\x7f\x00\x07\x01\x01\x04\x01'))
+ cia.append(TransparentStructureEF(parent=cia, fid=0x5032,
+ shortfid=0x12,
+ data='06\x02\x01\x01\x80\x11eSign '
+ 'Application\x03\x02\x06\xc0\xa2'
+ '\x1a0\x18\x02\x01\x01\x02\x02\x10A'
+ '\x05\x00\x03\x02\x06@\x06\t\x04\x00'
+ '\x7f\x00\x07\x01\x01\x04\x01'))
# EF.PrKD / EF.PrKDF
- cia.append(TransparentStructureEF(parent=cia, fid=0x4400, data='\xa080\x17\x0c\x0bPrK.ICC.QES\x03\x02\x07\x80\x04\x01\x01\x02\x01\x010\x15\x04\x01F\x03\x03\x06\x00@\x03\x02\x03\xb8\x02\x02\x00\x84\xa1\x03\x02\x01\x01\xa1\x060\x040\x02\x04\x00'))
+ cia.append(TransparentStructureEF(parent=cia, fid=0x4400,
+ data='\xa080\x17\x0c\x0bPrK.ICC.QES'
+ '\x03\x02\x07\x80\x04\x01\x01\x02'
+ '\x01\x010\x15\x04\x01F\x03\x03\x06'
+ '\x00@\x03\x02\x03\xb8\x02\x02\x00'
+ '\x84\xa1\x03\x02\x01\x01\xa1\x060'
+ '\x040\x02\x04\x00'))
# EF.PuKD / EF.PuKDF
- cia.append(TransparentStructureEF(parent=cia, fid=0x4404, data='050!\x0c\x1fZertifikat des ZDA f\xc3\xbcr die QES0\x06\x04\x01E\x01\x01\xff\xa1\x080\x060\x04\x04\x02\xc0\x000:0&\x0c$Zertifikat des Inhabers f\xc3\xbcr die QES0\x06\x04\x01F\x01\x01\x00\xa1\x080\x060\x04\x04\x02\xc0\x01'))
+ cia.append(TransparentStructureEF(parent=cia, fid=0x4404,
+ data='050!\x0c\x1fZertifikat des ZDA'
+ ' f\xc3\xbcr die QES0\x06\x04\x01E'
+ '\x01\x01\xff\xa1\x080\x060\x04\x04'
+ '\x02\xc0\x000:0&\x0c$Zertifikat des'
+ ' Inhabers f\xc3\xbcr die QES0\x06'
+ '\x04\x01F\x01\x01\x00\xa1\x080\x060'
+ '\x04\x04\x02\xc0\x01'))
# EF.AOD / EF.AODF
- cia.append(TransparentStructureEF(parent=cia, fid=0x4408, data='0/0\x0f\x0c\teSign-PIN\x03\x02\x06@0\x03\x04\x01\x01\xa1\x170\x15\x03\x03\x02H\x1c\n\x01\x01\x02\x01\x06\x02\x01\x00\x80\x01\x810\x02\x04\x00'))
+ cia.append(TransparentStructureEF(parent=cia, fid=0x4408,
+ data='0/0\x0f\x0c\teSign-PIN\x03\x02'
+ '\x06@0\x03\x04\x01\x01\xa1\x170\x15'
+ '\x03\x03\x02H\x1c\n\x01\x01\x02\x01'
+ '\x06\x02\x01\x00\x80\x01\x810\x02'
+ '\x04\x00'))
self.mf.append(cia)
# DF.eSign
- esign=DF(parent=self.mf, fid=0xfffd, dfname='\xA0\x00\x00\x01\x67\x45\x53\x49\x47\x4E')
+ esign = DF(parent=self.mf, fid=0xfffd,
+ dfname='\xA0\x00\x00\x01\x67\x45\x53\x49\x47\x4E')
# ZDA certificate
esign.append(TransparentStructureEF(parent=esign, fid=0xC000, data=''))
@@ -367,17 +605,66 @@ class CardGenerator(object):
esign.append(TransparentStructureEF(parent=esign, fid=0xC001, data=''))
self.mf.append(esign)
- self.sam = nPA_SAM(eid_pin="111111", can="222222", mrz="IDD<.
MAX_SHORT_LE = 0xff+1
MAX_EXTENDED_LE = 0xffff+1
-# Life cycle status byte
+# Life cycle status byte
LCB = {}
-LCB["NOINFORMATION"] = 0x00
-LCB["CREATION"] = 0x01
+LCB["NOINFORMATION"] = 0x00
+LCB["CREATION"] = 0x01
LCB["INITIALISATION"] = 0x03
-LCB["ACTIVATED"] = 0x05
-LCB["DEACTIVATED"] = 0x04
-LCB["TERMINATION"] = 0x0C
+LCB["ACTIVATED"] = 0x05
+LCB["DEACTIVATED"] = 0x04
+LCB["TERMINATION"] = 0x0C
-# Channel security attribute
+# Channel security attribute
CS = {}
-CS["NOTSHAREABLE"] = 0x01
-CS["SECURED"] = 0x02
+CS["NOTSHAREABLE"] = 0x01
+CS["SECURED"] = 0x02
CS["USERAUTHENTICATED"] = 0x03
-# Security attribute
-# Security attribute, Access mode byte for DFs/EFs
+# Security attribute
+# Security attribute, Access mode byte for DFs/EFs
SA = {}
-SA["AM_DF_DELETESELF"] = SA["AM_EF_DELETEFILE"] = 0x40
-SA["AM_DF_TERMINATEDF"] = SA["AM_EF_TERMINATEFILE"] = 0x20
-SA["AM_DF_ACTIVATEFILE"] = SA["AM_EF_ACTIVATEFILE"] = 0x10
+SA["AM_DF_DELETESELF"] = SA["AM_EF_DELETEFILE"] = 0x40
+SA["AM_DF_TERMINATEDF"] = SA["AM_EF_TERMINATEFILE"] = 0x20
+SA["AM_DF_ACTIVATEFILE"] = SA["AM_EF_ACTIVATEFILE"] = 0x10
SA["AM_DF_DEACTIVATEFILE"] = SA["AM_EF_DEACTIVATEFILE"] = 0x08
-SA["AM_DF_CREATEDF"] = SA["AM_EF_WRITEBINARY"] = 0x04
-SA["AM_DF_CREATEEF"] = SA["AM_EF_UPDATEBINARY"] = 0x02
-SA["AM_DF_DELETECHILD"] = SA["AM_EF_READBINARY"] = 0x01
+SA["AM_DF_CREATEDF"] = SA["AM_EF_WRITEBINARY"] = 0x04
+SA["AM_DF_CREATEEF"] = SA["AM_EF_UPDATEBINARY"] = 0x02
+SA["AM_DF_DELETECHILD"] = SA["AM_EF_READBINARY"] = 0x01
-# Security attribute in compact format, Security condition byte
-SA["CF_SC_NOCONDITION"] = 0x00
-SA["CF_SC_NEVER"] = 0xFF
-SA["__CF_SC_ATLEASTONE"] = 0x80
-SA["__CF_SC_ALLCONDITIONS"] = 0x00
-SA["__CF_SC_SECUREMESSAGING"] = 0x40
-SA["__CF_SC_EXTERNALAUTHENTICATION"] = 0x40
-SA["__CF_SC_USERAUTHENTICATION"] = 0x40
-SA["CF_SC_ONE_SECUREMESSAGING"] = SA["__CF_SC_ATLEASTONE"]|SA["__CF_SC_SECUREMESSAGING"]
-SA["CF_SC_ONE_EXTERNALAUTHENTICATION"] = SA["__CF_SC_ATLEASTONE"]|SA["__CF_SC_EXTERNALAUTHENTICATION"]
-SA["CF_SC_ONE_USERAUTHENTICATION"] = SA["__CF_SC_ATLEASTONE"]|SA["__CF_SC_USERAUTHENTICATION"]
-SA["CF_SC_ALL_SECUREMESSAGING"] = SA["__CF_SC_ALLCONDITIONS"]|SA["__CF_SC_SECUREMESSAGING"]
-SA["CF_SC_ALL_EXTERNALAUTHENTICATION"] = SA["__CF_SC_ALLCONDITIONS"]|SA["__CF_SC_EXTERNALAUTHENTICATION"]
-SA["CF_SC_ALL_USERAUTHENTICATION"] = SA["__CF_SC_ALLCONDITIONS"]|SA["__CF_SC_USERAUTHENTICATION"]
+# Security attribute in compact format, Security condition byte
+SA["CF_SC_NOCONDITION"] = 0x00
+SA["CF_SC_NEVER"] = 0xFF
+SA["__CF_SC_ATLEASTONE"] = 0x80
+SA["__CF_SC_ALLCONDITIONS"] = 0x00
+SA["__CF_SC_SECUREMESSAGING"] = 0x40
+SA["__CF_SC_EXTERNALAUTHENTICATION"] = 0x40
+SA["__CF_SC_USERAUTHENTICATION"] = 0x40
+SA["CF_SC_ONE_SECUREMESSAGING"] = SA["__CF_SC_ATLEASTONE"] | \
+ SA["__CF_SC_SECUREMESSAGING"]
+SA["CF_SC_ONE_EXTERNALAUTHENTICATION"] = SA["__CF_SC_ATLEASTONE"] | \
+ SA["__CF_SC_EXTERNALAUTHENTICATION"]
+SA["CF_SC_ONE_USERAUTHENTICATION"] = SA["__CF_SC_ATLEASTONE"] | \
+ SA["__CF_SC_USERAUTHENTICATION"]
+SA["CF_SC_ALL_SECUREMESSAGING"] = SA["__CF_SC_ALLCONDITIONS"] | \
+ SA["__CF_SC_SECUREMESSAGING"]
+SA["CF_SC_ALL_EXTERNALAUTHENTICATION"] = SA["__CF_SC_ALLCONDITIONS"] | \
+ SA["__CF_SC_EXTERNALAUTHENTICATION"]
+SA["CF_SC_ALL_USERAUTHENTICATION"] = SA["__CF_SC_ALLCONDITIONS"] | \
+ SA["__CF_SC_USERAUTHENTICATION"]
-# Data coding byte
+# Data coding byte
DCB = {}
-DCB["ONETIMEWRITE"] = 0x00
-DCB["PROPRIETARY"] = 0x20 # we use it for XOR
-DCB["WRITEOR"] = 0x40
-DCB["WRITEAND"] = 0x60
-DCB["BERTLV_FFVALID"] = 0x10
+DCB["ONETIMEWRITE"] = 0x00
+DCB["PROPRIETARY"] = 0x20 # we use it for XOR
+DCB["WRITEOR"] = 0x40
+DCB["WRITEAND"] = 0x60
+DCB["BERTLV_FFVALID"] = 0x10
DCB["BERTLV_FFINVALID"] = 0x10
-# File descriptor byte
+# File descriptor byte
FDB = {}
-FDB["NOTSHAREABLEFILE"] = 0x00
-FDB["SHAREABLEFILE"] = 0x40
-FDB["WORKINGEF"] = 0x00
-FDB["INTERNALEF"] = 0x80
-FDB["DF"] = 0x38
-FDB["EFSTRUCTURE_NOINFORMATIONGIVEN"] = 0x00
-FDB["EFSTRUCTURE_TRANSPARENT"] = 0x01
-FDB["EFSTRUCTURE_LINEAR_FIXED_NOFURTHERINFO"] = 0x02
-FDB["EFSTRUCTURE_LINEAR_FIXED_SIMPLETLV"] = 0x03
+FDB["NOTSHAREABLEFILE"] = 0x00
+FDB["SHAREABLEFILE"] = 0x40
+FDB["WORKINGEF"] = 0x00
+FDB["INTERNALEF"] = 0x80
+FDB["DF"] = 0x38
+FDB["EFSTRUCTURE_NOINFORMATIONGIVEN"] = 0x00
+FDB["EFSTRUCTURE_TRANSPARENT"] = 0x01
+FDB["EFSTRUCTURE_LINEAR_FIXED_NOFURTHERINFO"] = 0x02
+FDB["EFSTRUCTURE_LINEAR_FIXED_SIMPLETLV"] = 0x03
FDB["EFSTRUCTURE_LINEAR_VARIABLE_NOFURTHERINFO"] = 0x04
-FDB["EFSTRUCTURE_LINEAR_VARIABLESIMPLETLV"] = 0x05
-FDB["EFSTRUCTURE_CYCLIC_NOFURTHERINFO"] = 0x06
-FDB["EFSTRUCTURE_CYCLIC_SIMPLETLV"] = 0x07
+FDB["EFSTRUCTURE_LINEAR_VARIABLESIMPLETLV"] = 0x05
+FDB["EFSTRUCTURE_CYCLIC_NOFURTHERINFO"] = 0x06
+FDB["EFSTRUCTURE_CYCLIC_SIMPLETLV"] = 0x07
-# File identifier
+# File identifier
FID = {}
-FID["EFDIR"] = 0x2F00
-FID["EFATR"] = 0x2F00
-FID["MF"] = 0x3F00
+FID["EFDIR"] = 0x2F00
+FID["EFATR"] = 0x2F00
+FID["MF"] = 0x3F00
FID["PATHSELECTION"] = 0x3FFF
-FID["RESERVED"] = 0x3FFF
+FID["RESERVED"] = 0x3FFF
-#Secure Messaging constants
+# Secure Messaging constants
SM_Class = {}
-#Basic secure messaging objects
-#'80', '81' Plain value not encoded in BER-TLV
-SM_Class["PLAIN_VALUE_NO_TLV"] = 0x80
-SM_Class["PLAIN_VALUE_NO_TLV_ODD"] = 0x81
-# '82', '83' Cryptogram (plain value encoded in BER-TLV and including SM data objects, i.e., an SM field)
-SM_Class["CRYPTOGRAM_PLAIN_TLV_INCLUDING_SM"] = 0x82
-SM_Class["CRYPTOGRAM_PLAIN_TLV_INCLUDING_SM_ODD"] = 0x83
-#'84', '85' Cryptogram (plain value encoded in BER-TLV, but not including SM data objects)
-SM_Class["CRYPTOGRAM_PLAIN_TLV_NO_SM"] = 0x84
-SM_Class["CRYPTOGRAM_PLAIN_TLV_NO_SM_ODD"] = 0x85
-#'86', '87' Padding-content indicator byte followed by cryptogram (plain value not encoded in BER-TLV)
-SM_Class["CRYPTOGRAM_PADDING_INDICATOR"] = 0x86
-SM_Class["CRYPTOGRAM_PADDING_INDICATOR_ODD"] = 0x87
-#'89' Command header (CLA INS P1 P2, four bytes)
-SM_Class["PLAIN_COMMAND_HEADER"] = 0x89
+# Basic secure messaging objects
+# '80', '81' Plain value not encoded in BER-TLV
+SM_Class["PLAIN_VALUE_NO_TLV"] = 0x80
+SM_Class["PLAIN_VALUE_NO_TLV_ODD"] = 0x81
+# '82', '83' Cryptogram (plain value encoded in BER-TLV and including SM data
+# objects, i.e. a SM field)
+SM_Class["CRYPTOGRAM_PLAIN_TLV_INCLUDING_SM"] = 0x82
+SM_Class["CRYPTOGRAM_PLAIN_TLV_INCLUDING_SM_ODD"] = 0x83
+# '84', '85' Cryptogram (plain value encoded in BER-TLV, but not including SM
+# data objects)
+SM_Class["CRYPTOGRAM_PLAIN_TLV_NO_SM"] = 0x84
+SM_Class["CRYPTOGRAM_PLAIN_TLV_NO_SM_ODD"] = 0x85
+# '86', '87' Padding-content indicator byte followed by cryptogram (plain
+# value not encoded in BER-TLV)
+SM_Class["CRYPTOGRAM_PADDING_INDICATOR"] = 0x86
+SM_Class["CRYPTOGRAM_PADDING_INDICATOR_ODD"] = 0x87
+# '89' Command header (CLA INS P1 P2, four bytes)
+SM_Class["PLAIN_COMMAND_HEADER"] = 0x89
# '8E' Cryptographic checksum (at least four bytes)
-SM_Class["CHECKSUM"] = 0x8E
+SM_Class["CHECKSUM"] = 0x8E
# '90', '91' Hash-code
-SM_Class["HASH_CODE"] = 0x90
-SM_Class["HASH_CODE_ODD"] = 0x91
-#'92', '93' Certificate (data not encoded in BER-TLV)
-SM_Class["CERTIFICATE"] = 0x92
-SM_Class["CERTIFICATE_ODD"] = 0x93
-#'94', '95' Security environment identifier (SEID byte)
-SM_Class["SECURITY_ENIVRONMENT_ID"] = 0x94
-SM_Class["SECURITY_ENIVRONMENT_ID_ODD"] = 0x95
-#'96', '97' One or two bytes encoding Ne in the unsecured command-response pair (possibly empty)
-SM_Class["Ne"] = 0x96
-SM_Class["Ne_ODD"] = 0x97
-#'99' Processing status (SW1-SW2, two bytes; possibly empty)
-SM_Class["PLAIN_PROCESSING_STATUS"] = 0x99
-# '9A', '9B' Input data element for the computation of a digital signature (the value field is signed)
-SM_Class["INPUT_DATA_SIGNATURE_COMPUTATION"] = 0x9A
-SM_Class["INPUT_DATA_SIGNATURE_COMPUTATION_ODD"] = 0x9B
+SM_Class["HASH_CODE"] = 0x90
+SM_Class["HASH_CODE_ODD"] = 0x91
+# '92', '93' Certificate (data not encoded in BER-TLV)
+SM_Class["CERTIFICATE"] = 0x92
+SM_Class["CERTIFICATE_ODD"] = 0x93
+# '94', '95' Security environment identifier (SEID byte)
+SM_Class["SECURITY_ENIVRONMENT_ID"] = 0x94
+SM_Class["SECURITY_ENIVRONMENT_ID_ODD"] = 0x95
+# '96', '97' One or two bytes encoding Ne in the unsecured command-response
+# pair (possibly empty)
+SM_Class["Ne"] = 0x96
+SM_Class["Ne_ODD"] = 0x97
+# '99' Processing status (SW1-SW2, two bytes; possibly empty)
+SM_Class["PLAIN_PROCESSING_STATUS"] = 0x99
+# '9A', '9B' Input data element for the computation of a digital signature
+# (the value field is signed)
+SM_Class["INPUT_DATA_SIGNATURE_COMPUTATION"] = 0x9A
+SM_Class["INPUT_DATA_SIGNATURE_COMPUTATION_ODD"] = 0x9B
# '9C', '9D' Public key
-SM_Class["PUBLIC_KEY"] = 0x9C
-SM_Class["PUBLIC_KEY_ODD"] = 0x9D
+SM_Class["PUBLIC_KEY"] = 0x9C
+SM_Class["PUBLIC_KEY_ODD"] = 0x9D
# '9E' Digital signature
-SM_Class["DIGITAL_SIGNATURE"] = 0x9E
+SM_Class["DIGITAL_SIGNATURE"] = 0x9E
-#Auxiliary secure messaging objects
-#Input template for the computation of a hash-code (the template is hashed)
+# Auxiliary secure messaging objects
+# Input template for the computation of a hash-code (the template is hashed)
SM_Class["HASH_COMPUTATION_TEMPLATE"] = 0xA0
SM_Class["HASH_COMPUTATION_TEMPLATE_ODD"] = 0xA1
-#Input template for the verification of a cryptographic checksum (the template is included)
+# Input template for the verification of a cryptographic checksum (the
+# template is included)
SM_Class["CHECKSUM_VERIFICATION_TEMPLATE"] = 0xA2
-#Control reference template for authentication (AT)
+# Control reference template for authentication (AT)
SM_Class["AUTH_CRT"] = 0xA4
SM_Class["AUTH_CRT_ODD"] = 0xA5
-#Control reference template for key agreement (KAT)
+# Control reference template for key agreement (KAT)
SM_Class["KEY_AGREEMENT_CRT"] = 0xA6
SM_Class["KEY_AGREEMENT_CRT_ODD"] = 0xA7
-#Input template for the verification of a digital signature (the template is signed)
-SM_Class[0xA8] = "SIGNATURE_VERIFICATION_TEMPLATE"
-#Control reference template for hash-code (HT)
-SM_Class["HASH_CRT"] = 0xAA
+# Input template for the verification of a digital signature (the template is
+# signed)
+SM_Class[0xA8] = "SIGNATURE_VERIFICATION_TEMPLATE"
+# Control reference template for hash-code (HT)
+SM_Class["HASH_CRT"] = 0xAA
SM_Class["HASH_CRT_ODD"] = 0xAB
-#Input template for the computation of a digital signature (the concatenated value fields are signed)
-SM_Class["SIGNATURE_COMPUTATION_TEMPLATE"] = 0xAC
+# Input template for the computation of a digital signature (the concatenated
+# value fields are signed)
+SM_Class["SIGNATURE_COMPUTATION_TEMPLATE"] = 0xAC
SM_Class["SIGNATURE_COMPUTATION_TEMPLATE_ODD"] = 0xAD
-#Input template for the verification of a certificate (the concatenated value fields are certified)
+# Input template for the verification of a certificate (the concatenated value
+# fields are certified)
SM_Class["CERTIFICATE_VERIFICATION_TEMPLATE"] = 0xAE
SM_Class["CERTIFICATE_VERIFICATION_TEMPLATE_ODD"] = 0xAF
-#Plain value encoded in BER-TLV and including SM data objects, i.e., an SM field
-SM_Class["PLAIN_VALUE_TLV_INCULDING_SM"] = 0xB0
+# Plain value encoded in BER-TLV and including SM data objects, i.e. a SM
+# field
+SM_Class["PLAIN_VALUE_TLV_INCULDING_SM"] = 0xB0
SM_Class["PLAIN_VALUE_TLV_INCULDING_SM_ODD"] = 0xB1
-#Plain value encoded in BER-TLV, but not including SM data objects
+# Plain value encoded in BER-TLV, but not including SM data objects
SM_Class["PLAIN_VALUE_TLV_NO_SM"] = 0xB2
SM_Class["PLAIN_VALUE_TLV_NO_SM_ODD"] = 0xB3
-#Control reference template for cryptographic checksum (CCT)
-SM_Class["CHECKSUM_CRT"] =0xB4
-SM_Class["CHECKSUM_CRT_ODD"] =0xB5
-#Control reference template for digital signature (DST)
+# Control reference template for cryptographic checksum (CCT)
+SM_Class["CHECKSUM_CRT"] = 0xB4
+SM_Class["CHECKSUM_CRT_ODD"] = 0xB5
+# Control reference template for digital signature (DST)
SM_Class["SIGNATURE_CRT"] = 0xB6
SM_Class["SIGNATURE_CRT_ODD"] = 0xB7
-#Control reference template for confidentiality (CT)
+# Control reference template for confidentiality (CT)
SM_Class["CONFIDENTIALITY_CRT"] = 0xB8
SM_Class["CONFIDENTIALITY_CRT_ODD"] = 0xB9
-#Response descriptor template
+# Response descriptor template
SM_Class["RESPONSE_DESCRIPTOR_TEMPLATE"] = 0xBA
SM_Class["RESPONSE_DESCRIPTOR_TEMPLATE_ODD"] = 0xBB
-#Input template for the computation of a digital signature (the template is signed)
+# Input template for the computation of a digital signature (the template is
+# signed)
SM_Class["SIGNATURE_COMPUTATION_TEMPLATE"] = 0xBC
SM_Class["SIGNATURE_COMPUTATION_TEMPLATE_ODD"] = 0xBD
-#Input template for the verification of a certificate (the template is certified)
-SM_Class["CERTIFICATE_VERIFICATION_TEMPLATE"] = 0xBE
-#}}}
+# Input template for the verification of a certificate (the template is
+# certified)
+SM_Class["CERTIFICATE_VERIFICATION_TEMPLATE"] = 0xBE
+# }}}
-#Tags of control reference templates (CRT)
+# Tags of control reference templates (CRT)
CRT_TEMPLATE = {}
CRT_TEMPLATE["AT"] = 0xA4
CRT_TEMPLATE["KAT"] = 0xA6
CRT_TEMPLATE["HT"] = 0xAA
-CRT_TEMPLATE["CCT"] = 0xB4 # Template for Cryptographic Checksum
-CRT_TEMPLATE["DST"]= 0xB6
-CRT_TEMPLATE["CT"]= 0xB8 # Template for Confidentiality
+CRT_TEMPLATE["CCT"] = 0xB4 # Template for Cryptographic Checksum
+CRT_TEMPLATE["DST"] = 0xB6
+CRT_TEMPLATE["CT"] = 0xB8 # Template for Confidentiality
-#This table maps algorithms to numbers. It is used in the security environment
+# This table maps algorithms to numbers. It is used in the security environment
ALGO_MAPPING = {}
ALGO_MAPPING[0x00] = "DES3-ECB"
ALGO_MAPPING[0x01] = "DES3-CBC"
@@ -212,18 +230,17 @@ ALGO_MAPPING[0x0B] = "CC"
ALGO_MAPPING[0x0C] = "RSA"
ALGO_MAPPING[0x0D] = "DSA"
-# Recerence control for select command, and record handling commands
+# Recerence control for select command, and record handling commands
REF = {
- "IDENTIFIER_FIRST" : 0x00,
- "IDENTIFIER_LAST" : 0x01,
- "IDENTIFIER_NEXT" : 0x02,
- "IDENTIFIER_PREVIOUS" : 0x03,
- "IDENTIFIER_CONTROL" : 0x03,
- "NUMBER" : 0x04,
- "NUMBER_TO_LAST" : 0x05,
- "NUMBER_FROM_LAST" : 0x06,
- "NUMBER_CONTROL" : 0x07,
- "REFERENCE_CONTROL_RECORD" : 0x07,
- "REFERENCE_CONTROL_SELECT" : 0x03,
+ "IDENTIFIER_FIRST": 0x00,
+ "IDENTIFIER_LAST": 0x01,
+ "IDENTIFIER_NEXT": 0x02,
+ "IDENTIFIER_PREVIOUS": 0x03,
+ "IDENTIFIER_CONTROL": 0x03,
+ "NUMBER": 0x04,
+ "NUMBER_TO_LAST": 0x05,
+ "NUMBER_FROM_LAST": 0x06,
+ "NUMBER_CONTROL": 0x07,
+ "REFERENCE_CONTROL_RECORD": 0x07,
+ "REFERENCE_CONTROL_SELECT": 0x03,
}
-
diff --git a/virtualsmartcard/src/vpicc/virtualsmartcard/CryptoUtils.py b/virtualsmartcard/src/vpicc/virtualsmartcard/CryptoUtils.py
index b1112ee..d782ee6 100644
--- a/virtualsmartcard/src/vpicc/virtualsmartcard/CryptoUtils.py
+++ b/virtualsmartcard/src/vpicc/virtualsmartcard/CryptoUtils.py
@@ -1,33 +1,33 @@
#
# Copyright (C) 2014 Dominik Oepen
-#
+#
# This file is part of virtualsmartcard.
-#
+#
# 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
# Software Foundation, either version 3 of the License, or (at your option) any
# later version.
-#
+#
# virtualsmartcard is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
# more details.
-#
+#
# You should have received a copy of the GNU General Public License along with
# virtualsmartcard. If not, see .
#
-import sys, binascii, random, logging
+
+import logging
+import re
from struct import pack
-from binascii import b2a_hex, a2b_hex
from base64 import b64encode
from hashlib import pbkdf2_hmac
from random import randint
-from virtualsmartcard.utils import inttostring, hexdump
-import string, re
+from virtualsmartcard.utils import inttostring
try:
# Use PyCrypto (if available)
- from Crypto.Cipher import DES3, DES, AES, ARC4#@UnusedImport
+ from Crypto.Cipher import DES3, DES, AES, ARC4 # @UnusedImport
from Crypto.Hash import HMAC, SHA as SHA1
except ImportError:
@@ -37,25 +37,32 @@ except ImportError:
CYBERFLEX_IV = '\x00' * 8
-## *******************************************************************
-## * Generic methods *
-## *******************************************************************
-def get_cipher(cipherspec, key, iv = None):
+
+# *******************************************************************
+# * Generic methods *
+# *******************************************************************
+def get_cipher(cipherspec, key, iv=None):
cipherparts = cipherspec.split("-")
-
+
if len(cipherparts) > 2:
- raise ValueError('cipherspec must be of the form "cipher-mode" or "cipher"')
+ raise ValueError('cipherspec must be of the form "cipher-mode" or'
+ '"cipher"')
elif len(cipherparts) == 1:
cipherparts[1] = "ecb"
-
+
c_class = globals().get(cipherparts[0].upper(), None)
- if c_class is None:
- raise ValueError("Cipher '%s' not known, must be one of %s" % (cipherparts[0], ", ".join([e.lower() for e in dir() if e.isupper()])))
-
+ if c_class is None:
+ validCiphers = ",".join([e.lower() for e in dir() if e.isupper()])
+ raise ValueError("Cipher '%s' not known, must be one of %s" %
+ (cipherparts[0], validCiphers))
+
mode = getattr(c_class, "MODE_" + cipherparts[1].upper(), None)
if mode is None:
- raise ValueError("Mode '%s' not known, must be one of %s" % (cipherparts[1], ", ".join([e.split("_")[1].lower() for e in dir(c_class) if e.startswith("MODE_")])))
-
+ validModes = ", ".join([e.split("_")[1].lower() for e in dir(c_class)
+ if e.startswith("MODE_")])
+ raise ValueError("Mode '%s' not known, must be one of %s" %
+ (cipherparts[1], validModes))
+
cipher = None
if iv is None:
cipher = c_class.new(key, mode, '\x00'*get_cipher_blocklen(cipherspec))
@@ -64,16 +71,19 @@ def get_cipher(cipherspec, key, iv = None):
return cipher
+
def get_cipher_keylen(cipherspec):
cipherparts = cipherspec.split("-")
-
+
if len(cipherparts) > 2:
- raise ValueError('cipherspec must be of the form "cipher-mode" or "cipher"')
+ raise ValueError('cipherspec must be of the form "cipher-mode" or'
+ '"cipher"')
cipher = cipherparts[0].upper()
- #cipher = globals().get(cipherparts[0].upper(), None)
- #Note: return cipher.key_size does not work on Ubuntu, because e.g. AES.key_size == 0
- if cipher == "AES": #Pycrypto uses AES128
+ # cipher = globals().get(cipherparts[0].upper(), None)
+ # Note: return cipher.key_size does not work on Ubuntu, because e.g.
+ # AES.key_size == 0
+ if cipher == "AES": # Pycrypto uses AES128
return 16
elif cipher == "DES":
return 8
@@ -82,23 +92,26 @@ def get_cipher_keylen(cipherspec):
else:
raise ValueError("Unsupported Encryption Algorithm: " + cipher)
+
def get_cipher_blocklen(cipherspec):
cipherparts = cipherspec.split("-")
-
+
if len(cipherparts) > 2:
- raise ValueError('cipherspec must be of the form "cipher-mode" or "cipher"')
+ raise ValueError('cipherspec must be of the form "cipher-mode" or'
+ '"cipher"')
cipher = globals().get(cipherparts[0].upper(), None)
return cipher.block_size
+
def append_padding(blocklen, data, padding_class=0x01):
- """Append padding to the data.
+ """Append padding to the data.
Length of padding depends on length of data and the block size of the
specified encryption algorithm.
Different types of padding may be selected via the padding_class parameter
"""
- if padding_class == 0x01: #ISO padding
+ if padding_class == 0x01: # ISO padding
last_block_length = len(data) % blocklen
padding_length = blocklen - last_block_length
if padding_length == 0:
@@ -108,21 +121,23 @@ def append_padding(blocklen, data, padding_class=0x01):
return data + padding
+
def strip_padding(blocklen, data, padding_class=0x01):
"""
Strip the padding of decrypted data. Returns data without padding
"""
-
+
if padding_class == 0x01:
tail = len(data) - 1
while data[tail] != '\x80':
tail = tail - 1
return data[:tail]
-
+
+
def crypto_checksum(algo, key, data, iv=None, ssc=None):
"""
Compute various types of cryptographic checksums.
-
+
:param algo:
A string specifying the algorithm to use. Currently supported
algorithms are \"MAX\" \"HMAC\" and \"CC\" (Meaning a cryptographic
@@ -134,12 +149,12 @@ def crypto_checksum(algo, key, data, iv=None, ssc=None):
:param ssc:
Optional. A send sequence counter to be prepended to the data.
Only used by the \"CC\" algorithm
-
+
"""
-
+
if algo not in ("HMAC", "MAC", "CC"):
raise ValueError("Unknown Algorithm %s" % algo)
-
+
if algo == "MAC":
checksum = calculate_MAC(key, data, iv)
elif algo == "HMAC":
@@ -147,47 +162,52 @@ def crypto_checksum(algo, key, data, iv=None, ssc=None):
checksum = hmac.hexdigest()
del hmac
elif algo == "CC":
- if ssc != None:
- data = inttostring(ssc) + data
+ if ssc is not None:
+ data = inttostring(ssc) + data
a = cipher(True, "des-cbc", key[:8], data)
b = cipher(False, "des-ecb", key[8:16], a[-8:])
c = cipher(True, "des-ecb", key[:8], b)
checksum = c
-
+
return checksum
-def cipher(do_encrypt, cipherspec, key, data, iv = None):
+
+def cipher(do_encrypt, cipherspec, key, data, iv=None):
"""Do a cryptographic operation.
operation = do_encrypt ? encrypt : decrypt,
- cipherspec must be of the form "cipher-mode", or "cipher\""""
-
+ cipherspec must be of the form "cipher-mode", or "cipher\""""
+
cipher = get_cipher(cipherspec, key, iv)
-
+
result = None
if do_encrypt:
result = cipher.encrypt(data)
else:
result = cipher.decrypt(data)
-
+
del cipher
return result
-def encrypt(cipherspec, key, data, iv = None):
+
+def encrypt(cipherspec, key, data, iv=None):
return cipher(True, cipherspec, key, data, iv)
-def decrypt(cipherspec, key, data, iv = None):
+
+def decrypt(cipherspec, key, data, iv=None):
return cipher(False, cipherspec, key, data, iv)
+
def hash(hashmethod, data):
- from Crypto.Hash import SHA, MD5#, RIPEMD
+ from Crypto.Hash import SHA, MD5 # , RIPEMD
hash_class = locals().get(hashmethod.upper(), None)
- if hash_class == None:
+ if hash_class is None:
logging.error("Unknown Hash method %s" % hashmethod)
raise ValueError
hash = hash_class.new()
hash.update(data)
return hash.digest()
-
+
+
def operation_on_string(string1, string2, op):
if len(string1) != len(string2):
raise ValueError("string1 and string2 must be of equal length")
@@ -196,93 +216,97 @@ def operation_on_string(string1, string2, op):
result.append(chr(op(ord(string1[i]), ord(string2[i]))))
return "".join(result)
+
def protect_string(string, password, cipherspec=None):
"""
Encrypt and authenticate a string
"""
-
- #Derive a key and a salt from password
+
+ # Derive a key and a salt from password
pbk = crypt(password)
regex = re.compile('\$p5k2\$(\d*)\$([\w./]*)\$([\w./]*)')
match = regex.match(pbk)
- if match != None:
+ if match is not None:
iterations = match.group(1)
salt = match.group(2)
key = match.group(3)
else:
raise ValueError
-
- #Encrypt the string, authenticate and format it
- if cipherspec == None:
+
+ # Encrypt the string, authenticate and format it
+ if cipherspec is None:
cipherspec = "AES-CBC"
else:
- pass #TODO: Sanity check for cipher
+ pass # TODO: Sanity check for cipher
blocklength = get_cipher_blocklen(cipherspec)
- paddedString = append_padding(blocklength, string)
+ paddedString = append_padding(blocklength, string)
cryptedString = cipher(True, cipherspec, key, paddedString)
hmac = crypto_checksum("HMAC", key, cryptedString)
protectedString = "$p5k2$$" + salt + "$" + cryptedString + hmac
-
+
return protectedString
+
def read_protected_string(string, password, cipherspec=None):
"""
Decrypt a protected string and verify the authentication data
"""
- hmac_length = 32 #FIXME: Ugly
+ hmac_length = 32 # FIXME: Ugly
- #Check if the string has the structure, that is generated by protect_string
+ # Check if the string has the structure, that is generated by
+ # protect_string
regex = re.compile('\$p5k2\$\$([\w./]*)\$')
match = regex.match(string)
- if match != None:
+ if match is not None:
salt = match.group(1)
crypted = string[match.end():len(string) - hmac_length]
hmac = string[len(string) - hmac_length:]
else:
raise ValueError("Wrong string format")
-
- #Derive key
+
+ # Derive key
pbk = crypt(password, salt)
match = regex.match(pbk)
key = pbk[match.end():]
-
- #Verify HMAC
+
+ # Verify HMAC
checksum = crypto_checksum("HMAC", key, crypted)
if checksum != hmac:
logging.error("Found HMAC %s expected %s" % (str(hmac), str(checksum)))
raise ValueError("Failed to authenticate data. Wrong password?")
-
- #Decrypt data
- if cipherspec == None:
+
+ # Decrypt data
+ if cipherspec is None:
cipherspec = "AES-CBC"
decrypted = cipher(False, cipherspec, key, crypted)
-
+
return strip_padding(get_cipher_blocklen(cipherspec), decrypted)
-## *******************************************************************
-## * Cyberflex specific methods *
-## *******************************************************************
+
+# *******************************************************************
+# * Cyberflex specific methods *
+# *******************************************************************
def calculate_MAC(session_key, message, iv=CYBERFLEX_IV):
"""
Cyberflex MAC is the last Block of the input encrypted with DES3-CBC
"""
-
+
cipher = DES3.new(session_key, DES3.MODE_CBC, iv)
padded = append_padding(8, message, 0x01)
- block_count = len(padded) / cipher.block_size
crypted = cipher.encrypt(padded)
-
- return crypted[len(padded) - cipher.block_size : ]
+
+ return crypted[len(padded) - cipher.block_size:]
+
def crypt(word, salt=None, iterations=None):
"""PBKDF2-based unix crypt(3) replacement.
-
+
The number of iterations specified in the salt overrides the 'iterations'
parameter.
The effective hash length is 192 bits.
"""
-
+
# Generate a (pseudo-)random salt if the user hasn't provided one.
if salt is None:
salt = _makesalt()
@@ -293,7 +317,8 @@ def crypt(word, salt=None, iterations=None):
if not isinstance(salt, str):
raise TypeError("salt must be a string")
- # word must be a string or unicode (in the latter case, we convert to UTF-8)
+ # word must be a string or unicode (in the latter case, we convert to
+ # UTF-8)
if isinstance(word, unicode):
word = word.encode("UTF-8")
if not isinstance(word, str):
@@ -311,9 +336,10 @@ def crypt(word, salt=None, iterations=None):
iterations = converted
if not (iterations >= 1):
raise ValueError("Invalid salt")
-
+
# Make sure the salt matches the allowed character set
- allowed = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789./"
+ allowed = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ012345678"\
+ "9./"
for ch in salt:
if ch not in allowed:
raise ValueError("Illegal character %r in salt" % (ch,))
@@ -326,9 +352,10 @@ def crypt(word, salt=None, iterations=None):
rawhash = pbkdf2_hmac('sha1', salt, word, iterations, 24)
return salt + "$" + b64encode(rawhash, "./")
+
def _makesalt():
"""Return a 48-bit pseudorandom salt for crypt().
-
+
This function is not suitable for generating cryptographic secrets.
"""
binarysalt = "".join([pack("@H", randint(0, 0xffff)) for i in range(3)])
diff --git a/virtualsmartcard/src/vpicc/virtualsmartcard/SEutils.py b/virtualsmartcard/src/vpicc/virtualsmartcard/SEutils.py
index a791b2c..de699e2 100644
--- a/virtualsmartcard/src/vpicc/virtualsmartcard/SEutils.py
+++ b/virtualsmartcard/src/vpicc/virtualsmartcard/SEutils.py
@@ -1,50 +1,52 @@
#
# Copyright (C) 2011 Dominik Oepen
-#
+#
# This file is part of virtualsmartcard.
-#
+#
# 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
# Software Foundation, either version 3 of the License, or (at your option) any
# later version.
-#
+#
# virtualsmartcard is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
# more details.
-#
+#
# You should have received a copy of the GNU General Public License along with
# virtualsmartcard. If not, see .
-#
-from virtualsmartcard.TLVutils import pack, unpack, bertlv_pack
-from virtualsmartcard.ConstantDefinitions import CRT_TEMPLATE, ALGO_MAPPING, \
- SM_Class, MAX_EXTENDED_LE
-from virtualsmartcard.utils import inttostring, stringtoint, C_APDU
-from virtualsmartcard.SWutils import SwError, SW
-import virtualsmartcard.CryptoUtils as vsCrypto
from time import time
from random import seed, randint
+from virtualsmartcard.TLVutils import pack, unpack, bertlv_pack
+from virtualsmartcard.ConstantDefinitions import CRT_TEMPLATE, ALGO_MAPPING
+from virtualsmartcard.ConstantDefinitions import SM_Class, MAX_EXTENDED_LE
+from virtualsmartcard.utils import inttostring, stringtoint, C_APDU
+from virtualsmartcard.SWutils import SwError, SW
+import virtualsmartcard.CryptoUtils as vsCrypto
+
+
class ControlReferenceTemplate:
"""
- Control Reference Templates are used to configure the Security Environments.
- They specifiy which algorithms to use in which mode of operation and with
- which keys. There are six different types of Control Reference Template:
+ Control Reference Templates are used to configure the Security
+ Environments. They specify which algorithms to use in which mode of
+ operation and with which keys. There are six different types of Control
+ Reference Template:
HT, AT, KT, CCT, DST, CT-sym, CT-asym.
"""
def __init__(self, tag, config=""):
"""
Generates a new CRT
-
- :param tag: Indicates the type of the CRT (HT, AT, KT, CCT, DST, CT-sym,
- CT-asym)
+
+ :param tag: Indicates the type of the CRT (HT, AT, KT, CCT, DST,
+ CT-sym, CT-asym)
:param config: A string containing TLV encoded Security Environment
- parameters
+ parameters
"""
if tag not in (CRT_TEMPLATE["AT"], CRT_TEMPLATE["HT"],
- CRT_TEMPLATE["KAT"], CRT_TEMPLATE["CCT"],
- CRT_TEMPLATE["DST"], CRT_TEMPLATE["CT"]):
+ CRT_TEMPLATE["KAT"], CRT_TEMPLATE["CCT"],
+ CRT_TEMPLATE["DST"], CRT_TEMPLATE["CT"]):
raise ValueError("Unknown control reference tag.")
else:
self.type = tag
@@ -63,17 +65,17 @@ class ControlReferenceTemplate:
if config != "":
self.parse_SE_config(config)
self.__config_string = config
-
+
def parse_SE_config(self, config):
"""
Parse a control reference template as given e.g. in an MSE APDU.
-
- :param config: a TLV string containing the configuration for the CRT.
+
+ :param config: a TLV string containing the configuration for the CRT.
"""
error = False
structure = unpack(config)
for tlv in structure:
- tag, length, value = tlv
+ tag, length, value = tlv
if tag == 0x80:
self.__set_algo(value)
elif tag in (0x81, 0x82, 0x83, 0x84):
@@ -84,27 +86,27 @@ class ControlReferenceTemplate:
self.usage_qualifier = value
else:
error = True
-
+
if error:
raise SwError(SW["ERR_REFNOTUSABLE"])
else:
- return SW["NORMAL"], ""
-
+ return SW["NORMAL"], ""
+
def __set_algo(self, data):
"""
Set the algorithm to be used by this CRT. The algorithms are specified
in a global dictionary. New cards may add or modify this table in order
to support new or different algorithms.
-
+
:param data: reference to an algorithm
"""
-
- if not ALGO_MAPPING.has_key(data):
- raise SwError(SW["ERR_REFNOTUSABLE"])
+
+ if data not in ALGO_MAPPING:
+ raise SwError(SW["ERR_REFNOTUSABLE"])
else:
self.algorithm = ALGO_MAPPING[data]
self.__replace_tag(0x80, data)
-
+
def __set_key(self, tag, value):
if tag == 0x81:
self.fileref = value
@@ -115,13 +117,13 @@ class ControlReferenceTemplate:
self.keyref_public_key = value
elif tag == 0x84:
self.keyref_private_key = value
-
+
def __set_iv(self, tag, length, value):
iv = None
if tag == 0x85:
iv = 0x00
elif tag == 0x86:
- pass #What is initial chaining block?
+ pass # What is initial chaining block?
elif tag == 0x87 or tag == 0x93:
if length != 0:
iv = value
@@ -139,46 +141,47 @@ class ControlReferenceTemplate:
else:
iv = int(time())
self.iv = iv
-
+
def __replace_tag(self, tag, data):
"""
Adjust the config string using a given tag, value combination. If the
config string already contains a tag, value pair for the given tag,
- replace it. Otherwise append tag, length and value to the config string.
+ replace it. Otherwise append tag, length and value to the config
+ string.
"""
position = 0
while position < len(self.__config_string) and \
- self.__config_string[position] != tag:
+ self.__config_string[position] != tag:
length = stringtoint(self.__config_string[position+1])
position += length + 3
- if position < len(self.__config_string): #Replace Tag
+ if position < len(self.__config_string): # Replace Tag
length = stringtoint(self.__config_string[position+1])
self.__config_string = self.__config_string[:position] +\
- chr(tag) + inttostring(len(data)) + data +\
- self.__config_string[position+2+length:]
- else: #Add new tag
+ chr(tag) + inttostring(len(data)) + data +\
+ self.__config_string[position+2+length:]
+ else: # Add new tag
self.__config_string += chr(tag) + inttostring(len(data)) + data
-
+
def to_string(self):
"""
Return the content of the CRT, encoded as TLV data in a string
"""
return self.__config_string
-
+
def __str__(self):
return self.__config_string
-
-
+
+
class Security_Environment(object):
-
+
def __init__(self, MF, SAM):
self.sam = SAM
self.mf = MF
-
+
self.SEID = None
- #Control Reference Tables
+ # Control Reference Tables
self.at = ControlReferenceTemplate(CRT_TEMPLATE["AT"])
self.kat = ControlReferenceTemplate(CRT_TEMPLATE["KAT"])
self.ht = ControlReferenceTemplate(CRT_TEMPLATE["HT"])
@@ -197,7 +200,7 @@ class Security_Environment(object):
or to manipulate the various parameters of the current SE.
P1 specifies the operation to perform, p2 is either the SEID for the
referred SE or the tag of a control reference template
-
+
:param p1:
Bitmask according to this table
@@ -206,7 +209,7 @@ class Security_Environment(object):
== == == == == == == == =======================================
- - - 1 - - - - Secure messaging in command data field
- - 1 - - - - - Secure messaging in response data field
- - 1 - - - - - - Computation, decipherment, internal
+ - 1 - - - - - - Computation, decipherment, internal
authentication and key agreement
1 - - - - - - - Verification, encipherment, external
authentication and key agreement
@@ -217,24 +220,26 @@ class Security_Environment(object):
== == == == == == == == =======================================
"""
-
+
cmd = p1 & 0x0F
se = p1 >> 4
if(cmd == 0x01):
- #Secure messaging in command data field
+ # Secure messaging in command data field
if se & 0x01:
self.capdu_sm = True
- #Secure messaging in response data field
+ # Secure messaging in response data field
if se & 0x02:
self.rapdu_sm = True
- #Computation, decryption, internal authentication and key agreement
- if se & 0x04:
+ # Computation, decryption, internal authentication and key
+ # agreement
+ if se & 0x04:
self.internal_auth = True
- #Verification, encryption, external authentication and key agreement
+ # Verification, encryption, external authentication and key
+ # agreement
if se & 0x08:
self.external_auth = True
return self._set_SE(p2, data)
- elif(cmd== 0x02):
+ elif(cmd == 0x02):
return self.sam.store_SE(p2)
elif(cmd == 0x03):
return self.sam.restore_SE(p2)
@@ -242,14 +247,13 @@ class Security_Environment(object):
return self.sam.erase_SE(p2)
else:
raise SwError(SW["ERR_INCORRECTP1P2"])
-
def _set_SE(self, p2, data):
"""
Manipulate the current Security Environment. P2 is the tag of a
control reference template, data contains control reference objects
"""
-
+
if p2 == 0xA4:
return self.at.parse_SE_config(data)
elif p2 == 0xA6:
@@ -264,51 +268,54 @@ class Security_Environment(object):
return self.ct.parse_SE_config(data)
raise SwError(SW["ERR_INCORRECTP1P2"])
-
+
def parse_SM_CAPDU(self, CAPDU, authenticate_header):
"""
This methods parses a data field including Secure Messaging objects.
- SM_header indicates whether or not the header of the message shall be
+ SM_header indicates whether or not the header of the message shall be
authenticated. It returns an unprotected command APDU
-
+
:param CAPDU: The protected CAPDU to be parsed
:param authenticate_header: Whether or not the header should be
- included in authentication mechanisms
+ included in authentication mechanisms
:returns: Unprotected command APDU
"""
-
+
structure = unpack(CAPDU.data)
- return_data = ["",]
-
+ return_data = ["", ]
+
cla = None
ins = None
p1 = None
p2 = None
le = None
-
+
if authenticate_header:
- to_authenticate = inttostring(CAPDU.cla) + inttostring(CAPDU.ins)+\
- inttostring(CAPDU.p1) + inttostring(CAPDU.p2)
- to_authenticate = vsCrypto.append_padding(self.cct.blocklength, to_authenticate)
+ to_authenticate = inttostring(CAPDU.cla) +\
+ inttostring(CAPDU.ins) + inttostring(CAPDU.p1) +\
+ inttostring(CAPDU.p2)
+ to_authenticate = vsCrypto.append_padding(self.cct.blocklength,
+ to_authenticate)
else:
to_authenticate = ""
for tlv in structure:
tag, length, value = tlv
-
- if tag % 2 == 1: #Include object in checksum calculation
+
+ if tag % 2 == 1: # Include object in checksum calculation
to_authenticate += bertlv_pack([[tag, length, value]])
-
- #SM data objects for encapsulating plain values
+
+ # SM data objects for encapsulating plain values
if tag in (SM_Class["PLAIN_VALUE_NO_TLV"],
SM_Class["PLAIN_VALUE_NO_TLV_ODD"]):
- return_data.append(value) #FIXME: Need TLV coding?
- #Encapsulated SM objects. Parse them
- #FIXME: Need to pack value into a dummy CAPDU
+ return_data.append(value) # FIXME: Need TLV coding?
+ # Encapsulated SM objects. Parse them
+ # FIXME: Need to pack value into a dummy CAPDU
elif tag in (SM_Class["PLAIN_VALUE_TLV_INCULDING_SM"],
SM_Class["PLAIN_VALUE_TLV_INCULDING_SM_ODD"]):
- return_data.append(self.parse_SM_CAPDU(value, authenticate_header))
- #Encapsulated plaintext BER-TLV objects
+ return_data.append(self.parse_SM_CAPDU(value,
+ authenticate_header))
+ # Encapsulated plaintext BER-TLV objects
elif tag in (SM_Class["PLAIN_VALUE_TLV_NO_SM"],
SM_Class["PLAIN_VALUE_TLV_NO_SM_ODD"]):
return_data.append(value)
@@ -323,45 +330,47 @@ class Security_Environment(object):
p1 = value[4:6]
p2 = value[6:8]
- #SM data objects for confidentiality
+ # SM data objects for confidentiality
if tag in (SM_Class["CRYPTOGRAM_PLAIN_TLV_INCLUDING_SM"],
SM_Class["CRYPTOGRAM_PLAIN_TLV_INCLUDING_SM_ODD"]):
- #The cryptogram includes SM objects.
- #We decrypt them and parse the objects.
+ # The cryptogram includes SM objects.
+ # We decrypt them and parse the objects.
plain = self.decipher(tag, 0x80, value)
- #TODO: Need Le = length
- return_data.append(self.parse_SM_CAPDU(plain, authenticate_header))
+ # TODO: Need Le = length
+ return_data.append(self.parse_SM_CAPDU(plain,
+ authenticate_header))
elif tag in (SM_Class["CRYPTOGRAM_PLAIN_TLV_NO_SM"],
SM_Class["CRYPTOGRAM_PLAIN_TLV_NO_SM_ODD"]):
- #The cryptogram includes BER-TLV encoded plaintext.
- #We decrypt them and return the objects.
+ # The cryptogram includes BER-TLV encoded plaintext.
+ # We decrypt them and return the objects.
plain = self.decipher(tag, 0x80, value)
return_data.append(plain)
elif tag in (SM_Class["CRYPTOGRAM_PADDING_INDICATOR"],
SM_Class["CRYPTOGRAM_PADDING_INDICATOR_ODD"]):
- #The first byte of the data field indicates the padding to use:
+ # The first byte of the data field indicates the padding to use
"""
- Value Meaning
- '00' No further indication
- '01' Padding as specified in 6.2.3.1
- '02' No padding
- '1X' One to four secret keys for enciphering information,
- not keys ('X' is a bitmap with any value from '0' to 'F')
- '11' indicates the first key (e.g., an "even" control word
- in a pay TV system)
- '12' indicates the second key (e.g., an "odd" control word
- in a pay TV system)
- '13' indicates the first key followed by the second key
- (e.g., a pair of control words in a pay TV system)
- '2X' Secret key for enciphering keys, not information
- ('X' is a reference with any value from '0' to 'F')
- (e.g., in a pay TV system, either an operational key
- for enciphering control words, or a management key for
- enciphering operational keys)
- '3X' Private key of an asymmetric key pair ('X' is a
- reference with any value from '0' to 'F')
- '4X' Password ('X' is a reference with any value from '0' to
- 'F')
+ Value Meaning
+ '00' No further indication
+ '01' Padding as specified in 6.2.3.1
+ '02' No padding
+ '1X' One to four secret keys for enciphering information,
+ not keys ('X' is a bitmap with any value from '0' to
+ 'F')
+ '11' indicates the first key (e.g., an "even" control word
+ in a pay TV system)
+ '12' indicates the second key (e.g., an "odd" control word
+ in a pay TV system)
+ '13' indicates the first key followed by the second key
+ (e.g., a pair of control words in a pay TV system)
+ '2X' Secret key for enciphering keys, not information
+ ('X' is a reference with any value from '0' to 'F')
+ (e.g., in a pay TV system, either an operational key
+ for enciphering control words, or a management key for
+ enciphering operational keys)
+ '3X' Private key of an asymmetric key pair ('X' is a
+ reference with any value from '0' to 'F')
+ '4X' Password ('X' is a reference with any value from '0' to
+ 'F')
'80' to '8E' Proprietary
"""
padding_indicator = stringtoint(value[0])
@@ -370,16 +379,16 @@ class Security_Environment(object):
padding_indicator)
return_data.append(plain)
- #SM data objects for authentication
+ # SM data objects for authentication
if tag == SM_Class["CHECKSUM"]:
- auth = vsCrypto.append_padding(self.cct.blocklength, to_authenticate)
- checksum = self.compute_cryptographic_checksum(0x8E,
- 0x80,
- auth)
+ auth = vsCrypto.append_padding(self.cct.blocklength,
+ to_authenticate)
+ checksum = self.compute_cryptographic_checksum(0x8E, 0x80,
+ auth)
if checksum != value:
raise SwError(SW["ERR_SECMESSOBJECTSINCORRECT"])
elif tag == SM_Class["DIGITAL_SIGNATURE"]:
- auth = to_authenticate #FIXME: Need padding?
+ auth = to_authenticate # FIXME: Need padding?
signature = self.compute_digital_signature(0x9E, 0x9A, auth)
if signature != value:
raise SwError(SW["ERR_SECMESSOBJECTSINCORRECT"])
@@ -387,60 +396,63 @@ class Security_Environment(object):
hash = self.hash(p1, p2, to_authenticate)
if hash != value:
raise SwError(SW["ERR_SECMESSOBJECTSINCORRECT"])
-
- #Form unprotected CAPDU
- if cla == None:
+
+ # Form unprotected CAPDU
+ if cla is None:
cla = CAPDU.cla
- if ins == None:
+ if ins is None:
ins = CAPDU.ins
- if p1 == None:
+ if p1 is None:
p1 = CAPDU.p1
- if p2 == None:
+ if p2 is None:
p2 = CAPDU.p2
- # FIXME
- #if expected != "":
- #raise SwError(SW["ERR_SECMESSOBJECTSMISSING"])
+ # FIXME:
+ # if expected != "":
+ # raise SwError(SW["ERR_SECMESSOBJECTSMISSING"])
if isinstance(le, str):
- # FIXME C_APDU only handles le with strings of length 1. Better patch utils.py to support extended length apdus
+ # FIXME: C_APDU only handles le with strings of length 1.
+ # Better patch utils.py to support extended length apdus
le_int = stringtoint(le)
if le_int == 0 and len(le) > 1:
le_int = MAX_EXTENDED_LE
le = le_int
-
- c = C_APDU(cla=cla, ins=ins, p1=p1, p2=p2, le=le, data="".join(return_data))
+
+ c = C_APDU(cla=cla, ins=ins, p1=p1, p2=p2, le=le,
+ data="".join(return_data))
return c
def protect_response(self, sw, result):
"""
This method protects a response APDU using secure messaging mechanisms
-
+
:returns: the protected data and the SW bytes
"""
return_data = ""
- #if sw == SW["NORMAL"]:
+ # if sw == SW["NORMAL"]:
# sw = inttostring(sw)
- # length = len(sw)
+ # length = len(sw)
# tag = SM_Class["PLAIN_PROCESSING_STATUS"]
# tlv_sw = pack([(tag,length,sw)])
# return_data += tlv_sw
- if result != "": # Encrypt the data included in the RAPDU
+ if result != "": # Encrypt the data included in the RAPDU
encrypted = self.encipher(0x82, 0x80, result)
encrypted = "\x01" + encrypted
encrypted_tlv = pack([(
SM_Class["CRYPTOGRAM_PADDING_INDICATOR_ODD"],
len(encrypted),
encrypted)])
- return_data += encrypted_tlv
-
+ return_data += encrypted_tlv
+
if sw == SW["NORMAL"]:
- if self.cct.algorithm == None:
+ if self.cct.algorithm is None:
raise SwError(SW["CONDITIONSNOTSATISFIED"])
elif self.cct.algorithm == "CC":
tag = SM_Class["CHECKSUM"]
- padded = vsCrypto.append_padding(self.cct.blocklength, return_data)
+ padded = vsCrypto.append_padding(self.cct.blocklength,
+ return_data)
auth = self.compute_cryptographic_checksum(0x8E, 0x80, padded)
length = len(auth)
return_data += pack([(tag, length, auth)])
@@ -450,25 +462,25 @@ class Security_Environment(object):
auth = self.compute_digital_signature(0x9E, 0x9A, hash)
length = len(auth)
return_data += pack([(tag, length, auth)])
-
+
return sw, return_data
- #The following commands implement ISO 7816-8 {{{
+ # The following commands implement ISO 7816-8 {{{
def perform_security_operation(self, p1, p2, data):
"""
In the end this command is nothing but a big switch for all the other
commands in ISO 7816-8. It will invoke the appropriate command and
return its result
"""
-
+
allowed_P1P2 = ((0x90, 0x80), (0x90, 0xA0), (0x9E, 0x9A), (0x9E, 0xAC),
(0x9E, 0xBC), (0x00, 0xA2), (0x00, 0xA8), (0x00, 0x92),
(0x00, 0xAE), (0x00, 0xBE), (0x82, 0x80), (0x84, 0x80),
(0x86, 0x80), (0x80, 0x82), (0x80, 0x84), (0x80, 0x86))
-
+
if (p1, p2) not in allowed_P1P2:
raise SwError(SW["INCORRECTP1P2"])
-
+
if((p2 in (0x80, 0xA0)) and (p1 == 0x90)):
response_data = self.hash(p1, p2, data)
elif(p2 in (0x9A, 0xAC, 0xBC) and p1 == 0x9E):
@@ -483,13 +495,12 @@ class Security_Environment(object):
response_data = self.encipher(p1, p2, data)
elif (p2 in (0x82, 0x84, 0x86) and p1 == 0x80):
response_data = self.decipher(p1, p2, data)
-
+
if p1 == 0x00:
assert response_data == ""
-
+
return SW["NORMAL"], response_data
-
-
+
def compute_cryptographic_checksum(self, p1, p2, data):
"""
Compute a cryptographic checksum (e.g. MAC) for the given data.
@@ -497,54 +508,54 @@ class Security_Environment(object):
"""
if p1 != 0x8E or p2 != 0x80:
raise SwError(SW["ERR_INCORRECTP1P2"])
- if self.cct.key == None:
+ if self.cct.key is None:
raise SwError(SW["ERR_CONDITIONNOTSATISFIED"])
-
- checksum = vsCrypto.crypto_checksum(self.cct.algorithm, self.cct.key,
+
+ checksum = vsCrypto.crypto_checksum(self.cct.algorithm, self.cct.key,
data, self.cct.iv)
return checksum
-
+
def compute_digital_signature(self, p1, p2, data):
"""
Compute a digital signature for the given data.
Algorithm and key are specified in the current SE
-
+
:param p1: Must be 0x9E = Secure Messaging class for digital signatures
:param p2: Must be one of 0x9A, 0xAC, 0xBC. Indicates what kind of data
is included in the data field.
"""
-
- if p1 != 0x9E or not p2 in (0x9A, 0xAC, 0xBC):
+
+ if p1 != 0x9E or p2 not in (0x9A, 0xAC, 0xBC):
raise SwError(SW["ERR_INCORRECTP1P2"])
- if self.dst.key == None:
+ if self.dst.key is None:
raise SwError(SW["ERR_CONDITIONNOTSATISFIED"])
- to_sign = ""
- if p2 == 0x9A: #Data to be signed
+ to_sign = ""
+ if p2 == 0x9A: # Data to be signed
to_sign = data
- elif p2 == 0xAC: #Data objects, sign values
+ elif p2 == 0xAC: # Data objects, sign values
to_sign = ""
structure = unpack(data)
for tag, length, value in structure:
to_sign += value
- elif p2 == 0xBC: #Data objects to be signed
+ elif p2 == 0xBC: # Data objects to be signed
pass
-
+
signature = self.dst.key.sign(to_sign, "")
return signature
-
+
def hash(self, p1, p2, data):
"""
- Hash the given data using the algorithm specified by the current
+ Hash the given data using the algorithm specified by the current
Security environment.
-
+
:return: raw data (no TLV coding).
- """
- if p1 != 0x90 or not p2 in (0x80, 0xA0):
+ """
+ if p1 != 0x90 or p2 not in (0x80, 0xA0):
raise SwError(SW["ERR_INCORRECTP1P2"])
algo = self.ht.algorithm
- if algo == None:
+ if algo is None:
raise SwError(SW["ERR_CONDITIONNOTSATISFIED"])
try:
hash = vsCrypto.hash(algo, data)
@@ -565,7 +576,7 @@ class Security_Environment(object):
algo = self.cct.algorithm
key = self.cct.key
iv = self.cct.iv
- if algo == None or key == None:
+ if algo is None or key is None:
raise SwError(SW["ERR_CONDITIONNOTSATISFIED"])
structure = unpack(data)
@@ -593,14 +604,14 @@ class Security_Environment(object):
to_sign = ""
signature = ""
- if key == None:
+ if key is None:
raise SwError(SW["ERR_CONDITIONNOTSATISFIED"])
structure = unpack(data)
for tag, length, value in structure:
if tag == 0x9E:
signature = value
- elif tag == 0x9A: #FIXME: Correct treatment of all possible tags
+ elif tag == 0x9A: # FIXME: Correct treatment of all possible tags
to_sign = value
elif tag == 0xAC:
pass
@@ -631,15 +642,16 @@ class Security_Environment(object):
"""
Encipher data using key, algorithm, IV and Padding specified
by the current Security environment.
-
+
:returns: raw data (no TLV coding).
"""
algo = self.ct.algorithm
key = self.ct.key
- if key == None or algo == None:
+ if key is None or algo is None:
raise SwError(SW["ERR_CONDITIONNOTSATISFIED"])
else:
- padded = vsCrypto.append_padding(vsCrypto.get_cipher_blocklen(algo), data)
+ blocklen = vsCrypto.get_cipher_blocklen(algo)
+ padded = vsCrypto.append_padding(blocklen, data)
crypted = vsCrypto.encrypt(algo, key, padded, self.ct.iv)
return crypted
@@ -647,12 +659,12 @@ class Security_Environment(object):
"""
Decipher data using key, algorithm, IV and Padding specified
by the current Security environment.
-
+
:returns: raw data (no TLV coding). Padding is not removed!!!
"""
algo = self.ct.algorithm
key = self.ct.key
- if key == None or algo == None:
+ if key is None or algo is None:
raise SwError(SW["ERR_CONDITIONNOTSATISFIED"])
else:
plain = vsCrypto.decrypt(algo, key, data, self.ct.iv)
@@ -660,15 +672,17 @@ class Security_Environment(object):
def generate_public_key_pair(self, p1, p2, data):
"""
- The GENERATE PUBLIC-KEY PAIR command either initiates the generation
+ The GENERATE PUBLIC-KEY PAIR command either initiates the generation
and storing of a key pair, i.e., a public key and a private key, in the
card, or accesses a key pair previously generated in the card.
-
+
:param p1: should be 0x00 (generate new key)
- :param p2: '00' (no information provided) or reference of the key to be generated
- :param data: One or more CRTs associated to the key generation if P1-P2 different from '0000'
+ :param p2: '00' (no information provided) or reference of the key to
+ be generated
+ :param data: One or more CRTs associated to the key generation if
+ P1-P2 different from '0000'
"""
-
+
from Crypto.PublicKey import RSA, DSA
from Crypto.Util.randpool import RandomPool
rnd = RandomPool()
@@ -676,45 +690,45 @@ class Security_Environment(object):
cipher = self.ct.algorithm
c_class = locals().get(cipher, None)
- if c_class is None:
+ if c_class is None:
raise SwError(SW["ERR_CONDITIONNOTSATISFIED"])
- if p1 & 0x01 == 0x00: #Generate key
+ if p1 & 0x01 == 0x00: # Generate key
PublicKey = c_class.generate(self.dst.keylength, rnd.get_bytes)
self.dst.key = PublicKey
else:
- pass #Read key
+ pass # Read key
- #Encode keys
+ # Encode keys
if cipher == "RSA":
- #Public key
+ # Public key
n = str(PublicKey.__getstate__()['n'])
e = str(PublicKey.__getstate__()['e'])
pk = ((0x81, len(n), n), (0x82, len(e), e))
result = bertlv_pack(pk)
- #Private key
+ # Private key
d = PublicKey.__getstate__()['d']
elif cipher == "DSA":
- #DSAParams
+ # DSAParams
p = str(PublicKey.__getstate__()['p'])
q = str(PublicKey.__getstate__()['q'])
g = str(PublicKey.__getstate__()['g'])
- #Public key
+ # Public key
y = str(PublicKey.__getstate__()['y'])
- pk = ((0x81, len(p), p), (0x82, len(q), q), (0x83, len(g), g),
+ pk = ((0x81, len(p), p), (0x82, len(q), q), (0x83, len(g), g),
(0x84, len(y), y))
- #Private key
+ # Private key
x = str(PublicKey.__getstate__()['x'])
- #Add more algorithms here
- #elif cipher = "ECDSA":
+ # Add more algorithms here
+ # elif cipher = "ECDSA":
else:
raise SwError(SW["ERR_CONDITIONNOTSATISFIED"])
result = bertlv_pack([[0x7F49, len(pk), pk]])
- #TODO: Internally store key pair
+ # TODO: Internally store key pair
if p1 & 0x02 == 0x02:
- #We do not support extended header lists yet
+ # We do not support extended header lists yet
raise SwError["ERR_NOTSUPPORTED"]
else:
return SW["NORMAL"], result
diff --git a/virtualsmartcard/src/vpicc/virtualsmartcard/SWutils.py b/virtualsmartcard/src/vpicc/virtualsmartcard/SWutils.py
index a4e5788..d076292 100644
--- a/virtualsmartcard/src/vpicc/virtualsmartcard/SWutils.py
+++ b/virtualsmartcard/src/vpicc/virtualsmartcard/SWutils.py
@@ -1,129 +1,172 @@
#
# Copyright (C) 2011 Frank Morgner
-#
+#
# This file is part of virtualsmartcard.
-#
+#
# 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
# Software Foundation, either version 3 of the License, or (at your option) any
# later version.
-#
+#
# virtualsmartcard is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
# more details.
-#
+#
# You should have received a copy of the GNU General Public License along with
# virtualsmartcard. If not, see .
#
# Meaning of the interindustry values of SW1-SW2
SW = {
- "NORMAL" : 0x9000,
- "NORMAL_REST" : 0x6100,
- "WARN_NOINFO62" : 0x6200,
- "WARN_DATACORRUPTED" : 0x6281,
- "WARN_EOFBEFORENEREAD" : 0x6282,
- "WARN_DEACTIVATED" : 0x6283,
- "WARN_FCIFORMATTING" : 0x6284,
- "WARN_TERMINATIONSTATE" : 0x6285,
- "WARN_NOINPUTSENSOR" : 0x6286,
- "WARN_NOINFO63" : 0x6300,
- "WARN_FILEFILLED" : 0x6381,
- "ERR_EXECUTION" : 0x6400,
- "ERR_RESPONSEREQUIRED" : 0x6401,
- "ERR_NOINFO65" : 0x6500,
- "ERR_MEMFAILURE" : 0x6581,
- "ERR_WRONGLENGTH" : 0x6700,
- "ERR_NOINFO68" : 0x6800,
- "ERR_CHANNELNOTSUPPORTED" : 0x6881,
- "ERR_SECMESSNOTSUPPORTED" : 0x6882,
- "ERR_LASTCMDEXPECTED" : 0x6883,
- "ERR_CHAININGNOTSUPPORTED" : 0x6884,
- "ERR_NOINFO69" : 0x6900,
- "ERR_INCOMPATIBLEWITHFILE" : 0x6981,
- "ERR_SECSTATUS" : 0x6982,
- "ERR_AUTHBLOCKED" : 0x6983,
- "ERR_REFNOTUSABLE" : 0x6984,
- "ERR_CONDITIONNOTSATISFIED" : 0x6985,
- "ERR_NOCURRENTEF" : 0x6986,
- "ERR_SECMESSOBJECTSMISSING" : 0x6987,
- "ERR_SECMESSOBJECTSINCORRECT" : 0x6988,
- "ERR_NOINFO6A" : 0x6A00,
- "ERR_INCORRECTPARAMETERS" : 0x6A80,
- "ERR_NOTSUPPORTED" : 0x6A81,
- "ERR_FILENOTFOUND" : 0x6A82,
- "ERR_RECORDNOTFOUND" : 0x6A83,
- "ERR_NOTENOUGHMEMORY" : 0x6A84,
- "ERR_NCINCONSISTENTWITHTLV" : 0x6A85,
- "ERR_INCORRECTP1P2" : 0x6A86,
- "ERR_NCINCONSISTENTP1P2" : 0x6A87,
- "ERR_DATANOTFOUND" : 0x6A88,
- "ERR_FILEEXISTS" : 0x6A89,
- "ERR_DFNAMEEXISTS" : 0x6A8A,
- "ERR_OFFSETOUTOFFILE" : 0x6B00,
- "ERR_INSNOTSUPPORTED" : 0x6D00,
+ "NORMAL": 0x9000,
+ "NORMAL_REST": 0x6100,
+ "WARN_NOINFO62": 0x6200,
+ "WARN_DATACORRUPTED": 0x6281,
+ "WARN_EOFBEFORENEREAD": 0x6282,
+ "WARN_DEACTIVATED": 0x6283,
+ "WARN_FCIFORMATTING": 0x6284,
+ "WARN_TERMINATIONSTATE": 0x6285,
+ "WARN_NOINPUTSENSOR": 0x6286,
+ "WARN_NOINFO63": 0x6300,
+ "WARN_FILEFILLED": 0x6381,
+ "ERR_EXECUTION": 0x6400,
+ "ERR_RESPONSEREQUIRED": 0x6401,
+ "ERR_NOINFO65": 0x6500,
+ "ERR_MEMFAILURE": 0x6581,
+ "ERR_WRONGLENGTH": 0x6700,
+ "ERR_NOINFO68": 0x6800,
+ "ERR_CHANNELNOTSUPPORTED": 0x6881,
+ "ERR_SECMESSNOTSUPPORTED": 0x6882,
+ "ERR_LASTCMDEXPECTED": 0x6883,
+ "ERR_CHAININGNOTSUPPORTED": 0x6884,
+ "ERR_NOINFO69": 0x6900,
+ "ERR_INCOMPATIBLEWITHFILE": 0x6981,
+ "ERR_SECSTATUS": 0x6982,
+ "ERR_AUTHBLOCKED": 0x6983,
+ "ERR_REFNOTUSABLE": 0x6984,
+ "ERR_CONDITIONNOTSATISFIED": 0x6985,
+ "ERR_NOCURRENTEF": 0x6986,
+ "ERR_SECMESSOBJECTSMISSING": 0x6987,
+ "ERR_SECMESSOBJECTSINCORRECT": 0x6988,
+ "ERR_NOINFO6A": 0x6A00,
+ "ERR_INCORRECTPARAMETERS": 0x6A80,
+ "ERR_NOTSUPPORTED": 0x6A81,
+ "ERR_FILENOTFOUND": 0x6A82,
+ "ERR_RECORDNOTFOUND": 0x6A83,
+ "ERR_NOTENOUGHMEMORY": 0x6A84,
+ "ERR_NCINCONSISTENTWITHTLV": 0x6A85,
+ "ERR_INCORRECTP1P2": 0x6A86,
+ "ERR_NCINCONSISTENTP1P2": 0x6A87,
+ "ERR_DATANOTFOUND": 0x6A88,
+ "ERR_FILEEXISTS": 0x6A89,
+ "ERR_DFNAMEEXISTS": 0x6A8A,
+ "ERR_OFFSETOUTOFFILE": 0x6B00,
+ "ERR_INSNOTSUPPORTED": 0x6D00,
}
SW_MESSAGES = {
- 0x9000 : 'Normal processing (No further qualification)',
+ 0x9000: 'Normal processing (No further qualification)',
- 0x6200 : 'Warning processing (State of non-volatile memory is unchanged): No information given',
- 0x6281 : 'Warning processing (State of non-volatile memory is unchanged): Part of returned data may be corrupted',
- 0x6282 : 'Warning processing (State of non-volatile memory is unchanged): End of file or record reached before reading Ne bytes',
- 0x6283 : 'Warning processing (State of non-volatile memory is unchanged): Selected file deactivated',
- 0x6284 : 'Warning processing (State of non-volatile memory is unchanged): File control information not formatted according to 5.3.3',
- 0x6285 : 'Warning processing (State of non-volatile memory is unchanged): Selected file in termination state',
- 0x6286 : 'Warning processing (State of non-volatile memory is unchanged): No input data available from a sensor on the card',
+ 0x6200: 'Warning processing (State of non-volatile memory is unchanged): '
+ 'No information given',
+ 0x6281: 'Warning processing (State of non-volatile memory is unchanged): '
+ 'Part of returned data may be corrupted',
+ 0x6282: 'Warning processing (State of non-volatile memory is unchanged): '
+ 'End of file or record reached before reading Ne bytes',
+ 0x6283: 'Warning processing (State of non-volatile memory is unchanged): '
+ 'Selected file deactivated',
+ 0x6284: 'Warning processing (State of non-volatile memory is unchanged): '
+ 'File control information not formatted according to 5.3.3',
+ 0x6285: 'Warning processing (State of non-volatile memory is unchanged): '
+ 'Selected file in termination state',
+ 0x6286: 'Warning processing (State of non-volatile memory is unchanged): '
+ 'No input data available from a sensor on the card',
- 0x6300 : 'Warning processing (State of non-volatile memory has changed): No information given',
- 0x6381 : 'Warning processing (State of non-volatile memory has changed): File filled up by the last write',
+ 0x6300: 'Warning processing (State of non-volatile memory has changed): '
+ 'No information given',
+ 0x6381: 'Warning processing (State of non-volatile memory has changed): '
+ 'File filled up by the last write',
- 0x6400 : 'Execution error (State of non-volatile memory is unchanged): Execution error',
- 0x6401 : 'Execution error (State of non-volatile memory is unchanged): Immediate response required by the card',
+ 0x6400: 'Execution error (State of non-volatile memory is unchanged): '
+ 'Execution error',
+ 0x6401: 'Execution error (State of non-volatile memory is unchanged): '
+ 'Immediate response required by the card',
- 0x6500 : 'Execution error (State of non-volatile memory has changed): No information given',
- 0x6581 : 'Execution error (State of non-volatile memory has changed): Memory failure',
+ 0x6500: 'Execution error (State of non-volatile memory has changed): '
+ 'No information given',
+ 0x6581: 'Execution error (State of non-volatile memory has changed): '
+ 'Memory failure',
- 0x6700 : 'Checking error: Wrong length; no further indication',
+ 0x6700: 'Checking error: Wrong length; no further indication',
- 0x6800 : 'Checking error (Functions in CLA not supported): No information given',
- 0x6881 : 'Checking error (Functions in CLA not supported): Logical channel not supported',
- 0x6882 : 'Checking error (Functions in CLA not supported): Secure messaging not supported',
- 0x6883 : 'Checking error (Functions in CLA not supported): Last command of the chain expected',
- 0x6884 : 'Checking error (Functions in CLA not supported): Command chaining not supported',
+ 0x6800: 'Checking error (Functions in CLA not supported): '
+ 'No information given',
+ 0x6881: 'Checking error (Functions in CLA not supported): '
+ 'Logical channel not supported',
+ 0x6882: 'Checking error (Functions in CLA not supported): '
+ 'Secure messaging not supported',
+ 0x6883: 'Checking error (Functions in CLA not supported): '
+ 'Last command of the chain expected',
+ 0x6884: 'Checking error (Functions in CLA not supported): '
+ 'Command chaining not supported',
- 0x6900 : 'Checking error (Command not allowed): No information given',
- 0x6981 : 'Checking error (Command not allowed): Command incompatible with file structure',
- 0x6982 : 'Checking error (Command not allowed): Security status not satisfied',
- 0x6983 : 'Checking error (Command not allowed): Authentication method blocked',
- 0x6984 : 'Checking error (Command not allowed): Reference data not usable',
- 0x6985 : 'Checking error (Command not allowed): Conditions of use not satisfied',
- 0x6986 : 'Checking error (Command not allowed): Command not allowed (no current EF)',
- 0x6987 : 'Checking error (Command not allowed): Expected secure messaging data objects missing',
- 0x6988 : 'Checking error (Command not allowed): Incorrect secure messaging data objects',
+ 0x6900: 'Checking error (Command not allowed): '
+ 'No information given',
+ 0x6981: 'Checking error (Command not allowed): '
+ 'Command incompatible with file structure',
+ 0x6982: 'Checking error (Command not allowed): '
+ 'Security status not satisfied',
+ 0x6983: 'Checking error (Command not allowed): '
+ 'Authentication method blocked',
+ 0x6984: 'Checking error (Command not allowed): '
+ 'Reference data not usable',
+ 0x6985: 'Checking error (Command not allowed): '
+ 'Conditions of use not satisfied',
+ 0x6986: 'Checking error (Command not allowed): '
+ 'Command not allowed (no current EF)',
+ 0x6987: 'Checking error (Command not allowed): '
+ 'Expected secure messaging data objects missing',
+ 0x6988: 'Checking error (Command not allowed): '
+ 'Incorrect secure messaging data objects',
- 0x6A00 : 'Checking error (Wrong parameters P1-P2): No information given',
- 0x6A80 : 'Checking error (Wrong parameters P1-P2): Incorrect parameters in the command data field',
- 0x6A81 : 'Checking error (Wrong parameters P1-P2): Function not supported',
- 0x6A82 : 'Checking error (Wrong parameters P1-P2): File or application not found',
- 0x6A83 : 'Checking error (Wrong parameters P1-P2): Record not found',
- 0x6A84 : 'Checking error (Wrong parameters P1-P2): Not enough memory space in the file',
- 0x6A85 : 'Checking error (Wrong parameters P1-P2): Nc inconsistent with TLV structure',
- 0x6A86 : 'Checking error (Wrong parameters P1-P2): Incorrect parameters P1-P2',
- 0x6A87 : 'Checking error (Wrong parameters P1-P2): Nc inconsistent with parameters P1-P2',
- 0x6A88 : 'Checking error (Wrong parameters P1-P2): Referenced data or reference data not found (exact meaning depending on the command)',
- 0x6A89 : 'Checking error (Wrong parameters P1-P2): File already exists',
- 0x6A8A : 'Checking error (Wrong parameters P1-P2): DF name already exists',
+ 0x6A00: 'Checking error (Wrong parameters P1-P2): '
+ 'No information given',
+ 0x6A80: 'Checking error (Wrong parameters P1-P2): '
+ 'Incorrect parameters in the command data field',
+ 0x6A81: 'Checking error (Wrong parameters P1-P2): '
+ 'Function not supported',
+ 0x6A82: 'Checking error (Wrong parameters P1-P2): '
+ 'File or application not found',
+ 0x6A83: 'Checking error (Wrong parameters P1-P2): '
+ 'Record not found',
+ 0x6A84: 'Checking error (Wrong parameters P1-P2): '
+ 'Not enough memory space in the file',
+ 0x6A85: 'Checking error (Wrong parameters P1-P2): '
+ 'Nc inconsistent with TLV structure',
+ 0x6A86: 'Checking error (Wrong parameters P1-P2): '
+ 'Incorrect parameters P1-P2',
+ 0x6A87: 'Checking error (Wrong parameters P1-P2): '
+ 'Nc inconsistent with parameters P1-P2',
+ 0x6A88: 'Checking error (Wrong parameters P1-P2): '
+ 'Referenced data or reference data not found (exact meaning '
+ 'depending on the command)',
+ 0x6A89: 'Checking error (Wrong parameters P1-P2): '
+ 'File already exists',
+ 0x6A8A: 'Checking error (Wrong parameters P1-P2): '
+ 'DF name already exists',
- 0x6B00 : 'Checking error (Wrong parameters P1-P2): Wrong parameters (offset outside the EF)',
- 0x6D00 : 'Checking error (Instruction code not supported or invalid)',
- 0x6E00 : 'Checking error (Class not supported)',
- 0x6F00 : 'Checking error (No precise diagnosis)',
+ 0x6B00: 'Checking error (Wrong parameters P1-P2): '
+ 'Wrong parameters (offset outside the EF)',
+ 0x6D00: 'Checking error (Instruction code not supported or invalid)',
+ 0x6E00: 'Checking error (Class not supported)',
+ 0x6F00: 'Checking error (No precise diagnosis)',
}
for i in range(0, 0xff):
- SW_MESSAGES[0x6100 + i] = 'Normal Processing (%d data bytes still available)' % i
+ SW_MESSAGES[0x6100 + i] = 'Normal Processing (' + str(i) + \
+ ' data bytes still available)'
SW_MESSAGES[0x6600 + i] = 'Execution error (Security-related issues)'
- SW_MESSAGES[0x6C00 + i] = 'Checking error (Wrong Le field; %d available data bytes)' % i
+ SW_MESSAGES[0x6C00 + i] = 'Checking error (Wrong Le field; ' + str(i) + \
+ ' available data bytes)'
class SwError(Exception):
diff --git a/virtualsmartcard/src/vpicc/virtualsmartcard/SmartcardFilesystem.py b/virtualsmartcard/src/vpicc/virtualsmartcard/SmartcardFilesystem.py
index 2d58958..477af74 100644
--- a/virtualsmartcard/src/vpicc/virtualsmartcard/SmartcardFilesystem.py
+++ b/virtualsmartcard/src/vpicc/virtualsmartcard/SmartcardFilesystem.py
@@ -1,24 +1,24 @@
#
# Copyright (C) 2011 Frank Morgner
-#
+#
# This file is part of virtualsmartcard.
-#
+#
# 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
# Software Foundation, either version 3 of the License, or (at your option) any
# later version.
-#
+#
# virtualsmartcard is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
# more details.
-#
+#
# You should have received a copy of the GNU General Public License along with
# virtualsmartcard. If not, see .
#
-#TODO: use bertlv_pack for fdm
-#TODO: zu lange daten abschneiden und trotzdem tlv laenge beibehalten
+# TODO: use bertlv_pack for fdm
+# TODO: zu lange daten abschneiden und trotzdem tlv laenge beibehalten
from pickle import dumps, loads
import logging
@@ -27,7 +27,8 @@ from virtualsmartcard.TLVutils import *
from virtualsmartcard.SWutils import SW, SwError
from virtualsmartcard.utils import stringtoint, inttostring, hexdump
-def isEqual(list):
+
+def isEqual(list):
"""Returns True, if all items are equal, otherwise False"""
if len(list) > 1:
for item in list:
@@ -37,7 +38,7 @@ def isEqual(list):
return True
-def walk(start, path):
+def walk(start, path):
"""Walks a path of fids and returns the last file (EF or DF).
:param start: DF, where to look for the first fid
@@ -56,7 +57,7 @@ def walk(start, path):
return start
-def getfile_byrefdataobj(mf, refdataobjs):
+def getfile_byrefdataobj(mf, refdataobjs):
"""Returns a list of files according to the given list of reference data
objects.
@@ -74,7 +75,8 @@ def getfile_byrefdataobj(mf, refdataobjs):
elif length <= 2:
newvalue = stringtoint(newvalue)
if length == 1:
- if newvalue & 5 != 0 or (newvalue >> 3) == 0 or (newvalue>>3) == (0xff>>3):
+ if (newvalue & 5 != 0 or (newvalue >> 3) == 0 or
+ (newvalue >> 3) == (0xff >> 3)):
raise SwError(SW["ERR_INCORRECTPARAMETERS"])
else:
file = mf.select('shortfid', newvalue >> 3)
@@ -99,22 +101,23 @@ def getfile_byrefdataobj(mf, refdataobjs):
return result
-def write(old, newlist, offsets, datacoding, maxsize=None):
+def write(old, newlist, offsets, datacoding, maxsize=None):
"""Returns the status bytes and the result of a write operation according to
the given data coding.
:param old: string of old data
:param newlist: a list of new data string
:param offsets: a list of offsets, each for one new data strings
- :param datacoding: DCB["ONETIMEWRITE"] (replace) or DCB["WRITEOR"] (logical or)
- or DCB["WRITEAND"] (logical and) or DCB["PROPRIETARY"] (logical xor)
+ :param datacoding: DCB["ONETIMEWRITE"] (replace) or DCB["WRITEOR"]
+ (logical or) or DCB["WRITEAND"] (logical and) or
+ DCB["PROPRIETARY"] (logical xor)
:param maxsize: the maximum number of bytes in the result
"""
- result = old
+ result = old
listindex = 0
while listindex < len(offsets) and listindex < len(newlist):
- offset = offsets[listindex]
- new = newlist[listindex]
+ offset = offsets[listindex]
+ new = newlist[listindex]
writenow = len(new)
if offset > len(result):
@@ -123,15 +126,15 @@ def write(old, newlist, offsets, datacoding, maxsize=None):
raise SwError(SW["ERR_NOTENOUGHMEMORY"], old)
if datacoding == DCB["ONETIMEWRITE"]:
- result = (result[ 0 : offset ] +
- new[ 0 : writenow ] +
- result[ offset+writenow : len(result) ])
+ result = (result[0:offset] +
+ new[0:writenow] +
+ result[offset + writenow:len(result)])
else:
if offset + writenow > len(old):
raise SwError(SW["ERR_NOTENOUGHMEMORY"], old)
- newindex = 0
+ newindex = 0
resultindex = offset + newindex
while newindex < writenow:
if datacoding == DCB["WRITEOR"]:
@@ -145,8 +148,8 @@ def write(old, newlist, offsets, datacoding, maxsize=None):
newpiece = chr(
ord(result[resultindex]) ^ ord(new[newindex]))
result = (result[0:resultindex] + newpiece +
- result[resultindex+1:len(result)])
- newindex = newindex + 1
+ result[resultindex+1:len(result)])
+ newindex = newindex + 1
resultindex = resultindex + 1
listindex = listindex + 1
@@ -154,29 +157,28 @@ def write(old, newlist, offsets, datacoding, maxsize=None):
return result
-def get_indexes(items, reference=REF["IDENTIFIER_FIRST"], index_current=0):
+def get_indexes(items, reference=REF["IDENTIFIER_FIRST"], index_current=0):
"""
Returns all indexes of the list, which are specified by 'reference' and by
the current index 'index_current' (-1 for no current item) in the correct
order. I. e.:
- REF["IDENTIFIER_FIRST"] : all indexes from first to the last item
- REF["IDENTIFIER_LAST"] : all indexes from the last to first item
- REF["IDENTIFIER_NEXT"] : all indexes from the next to the last item
- REF["IDENTIFIER_PREVIOUS"] : all indexes from the previous to the first item
+ REF["IDENTIFIER_FIRST"]: all indexes from first to the last item
+ REF["IDENTIFIER_LAST"]: all indexes from the last to first item
+ REF["IDENTIFIER_NEXT"]: all indexes from the next to the last item
+ REF["IDENTIFIER_PREVIOUS"]: all indexes from the previous to the first item
"""
- if (reference in [REF["IDENTIFIER_FIRST"],
- REF["IDENTIFIER_LAST"]]
- or index_current == -1):
+ if (reference in [REF["IDENTIFIER_FIRST"], REF["IDENTIFIER_LAST"]] or
+ index_current == -1):
# Read first occurrence OR
# Read last occurrence OR
# No current record (and next/previous occurrence)
indexes = range(0, len(items))
- if (reference == REF["IDENTIFIER_LAST"]
- or reference == REF["IDENTIFIER_PREVIOUS"]):
+ if (reference == REF["IDENTIFIER_LAST"] or
+ reference == REF["IDENTIFIER_PREVIOUS"]):
# Read last occurrence OR
# No current record and previous occurrence
- indexes.reverse();
+ indexes.reverse()
elif reference == REF["IDENTIFIER_PREVIOUS"]:
# Read previous occurrence
indexes = range(0, index_current)
@@ -187,7 +189,7 @@ def get_indexes(items, reference=REF["IDENTIFIER_FIRST"], index_current=0):
return indexes
-def prettyprint_anything(indent, thing):
+def prettyprint_anything(indent, thing):
"""
Returns a recursively generated string representation of an object and its
attributes.
@@ -196,18 +198,20 @@ def prettyprint_anything(indent, thing):
indent = indent + " "
for (attribute, newvalue) in thing.__dict__.items():
if isinstance(newvalue, int):
- s = s + "\n" + indent + attribute + (16-len(attribute))*" " + "0x%x" % (newvalue)
+ s = s + "\n" + indent + attribute + (16-len(attribute)) * " " +\
+ "0x%x" % (newvalue)
elif isinstance(newvalue, str):
- s = s + "\n" + indent + attribute + (16-len(attribute))*" " + "length %d:" % len(newvalue)
+ s = s + "\n" + indent + attribute + (16-len(attribute)) * " " +\
+ "length %d:" % len(newvalue)
s = s + "\n" + indent + hexdump(newvalue, len(indent))
elif isinstance(newvalue, list):
- s = s + "\n" + indent + attribute + (16-len(attribute))*" "
+ s = s + "\n" + indent + attribute + (16 - len(attribute)) * " "
for item in newvalue:
s = s + "\n" + prettyprint_anything(indent + " ", item)
return s
-def make_property(prop, doc):
+def make_property(prop, doc):
"""
Assigns a property to the calling object. This is used to decorate instance
variables with docstrings.
@@ -219,26 +223,33 @@ def make_property(prop, doc):
doc)
-
class File(object):
"""Template class for a smartcard file."""
- bertlv_data = make_property("bertlv_data", "list of (tag, length, value)-tuples of BER-TLV coded data objects (encrypted)")
- lifecycle = make_property("lifecycle", "life cycle byte")
- parent = make_property("parent ", "parent DF")
- fid = make_property("fid", "file identifier")
+ bertlv_data = make_property("bertlv_data", "list of (tag, length, "
+ "value)-tuples of BER-TLV "
+ "coded data objects "
+ "(encrypted)")
+ lifecycle = make_property("lifecycle", "life cycle byte")
+ parent = make_property("parent ", "parent DF")
+ fid = make_property("fid", "file identifier")
filedescriptor = make_property("filedescriptor", "file descriptor byte")
- simpletlv_data = make_property("simpletlv_data", "list of (tag, length, value)-tuples of SIMPLE-TLV coded data objects (encrypted)")
+ simpletlv_data = make_property("simpletlv_data", "list of (tag, length, "
+ "value)-tuples of "
+ "SIMPLE-TLV coded data "
+ "objects (encrypted)")
+
def __init__(self, parent, fid, filedescriptor,
- lifecycle=LCB["ACTIVATED"],
- simpletlv_data=None,
- bertlv_data=None,
- SAM=None,
- extra_fci_data=''):
+ lifecycle=LCB["ACTIVATED"],
+ simpletlv_data=None,
+ bertlv_data=None,
+ SAM=None,
+ extra_fci_data=''):
"""
The constructor is supposed to be involved by creation of a DF or EF.
"""
- if (fid>0xFFFF or fid<0 or fid in [FID["PATHSELECTION"],
- FID["RESERVED"]] or filedescriptor>0xFF or lifecycle>0xFF):
+ if (fid > 0xFFFF or fid < 0 or fid in
+ [FID["PATHSELECTION"], FID["RESERVED"]] or
+ filedescriptor > 0xFF or lifecycle > 0xFF):
raise SwError(SW["ERR_INCORRECTPARAMETERS"])
self.lifecycle = lifecycle
self.parent = parent
@@ -251,21 +262,23 @@ class File(object):
self.extra_fci_data = extra_fci_data
if simpletlv_data:
if not isinstance(simpletlv_data, list):
- raise TypeError("must be a list of (tag, length, value)-tuples")
+ raise TypeError("must be a list of (tag, length, "
+ "value)-tuples")
self.simpletlv_data = simpletlv_data
if bertlv_data:
if not isinstance(bertlv_data, list):
- raise TypeError("must be a list of (tag, length, value)-tuples")
+ raise TypeError("must be a list of (tag, length, "
+ "value)-tuples")
self.bertlv_data = bertlv_data
def decrypt(self, path, data):
- if self.SAM == None: #WARNING: Fails silent
+ if self.SAM is None: # WARNING: Fails silent
return data
else:
return self.SAM.FSencrypt(path, data)
-
+
def encrypt(self, path, data):
- if self.SAM == None: #WARNING: Fails silent
+ if self.SAM is None: # WARNING: Fails silent
return data
else:
return self.SAM.FSdecrypt(path, data)
@@ -277,7 +290,7 @@ class File(object):
def getpath(self):
"""Returns the path to this file beginning with the MF's fid."""
- if self.parent == None:
+ if self.parent is None:
return inttostring(self.fid, 2)
else:
return self.parent.getpath() + inttostring(self.fid, 2)
@@ -341,7 +354,7 @@ class File(object):
if t == tag:
# TODO: what if multiple tags can be found?
value = write(oldvalue, [newvalue], [0], newlength,
- self.datacoding)
+ self.datacoding)
tlv_data[i] = (tag, len(value), value)
tagfound = True
if not tagfound:
@@ -385,22 +398,25 @@ class File(object):
raise SwError(SW["ERR_INCOMPATIBLEWITHFILE"])
-
-class DF(File):
+class DF(File):
"""Class for a dedicated file"""
- data = make_property("data", "unknown")
+ data = make_property("data", "unknown")
content = make_property("content", "list of files of the DF")
- dfname = make_property("dfname", "string with up to 16 bytes. DF name, which can also be used as application identifier.")
- def __init__(self, parent, fid, filedescriptor=FDB["NOTSHAREABLEFILE"]|FDB["DF"],
- lifecycle=LCB["ACTIVATED"],
- simpletlv_data=None, bertlv_data=None, dfname=None, data=""):
+ dfname = make_property("dfname", "string with up to 16 bytes. DF name,"
+ "which can also be used as application"
+ "identifier.")
+
+ def __init__(self, parent, fid,
+ filedescriptor=FDB["NOTSHAREABLEFILE"] | FDB["DF"],
+ lifecycle=LCB["ACTIVATED"],
+ simpletlv_data=None, bertlv_data=None, dfname=None, data=""):
"""
See File for more.
"""
File.__init__(self, parent, fid, filedescriptor, lifecycle,
- simpletlv_data, bertlv_data)
+ simpletlv_data, bertlv_data)
if dfname:
- if len(dfname)>16:
+ if len(dfname) > 16:
raise SwError(SW["ERR_INCORRECTPARAMETERS"])
self.dfname = dfname
self.content = []
@@ -450,15 +466,17 @@ class DF(File):
for f in self.content:
if f.fid == file.fid:
raise SwError(SW["ERR_FILEEXISTS"])
- if hasattr(f, 'dfname') and hasattr(file, 'dfname') and f.dfname == file.dfname:
+ if (hasattr(f, 'dfname') and hasattr(file, 'dfname') and
+ f.dfname == file.dfname):
raise SwError(SW["ERR_DFNAMEEXISTS"])
- if hasattr(f, 'shortfid') and hasattr(file, 'shortfid') and f.shortfid == file.shortfid:
+ if (hasattr(f, 'shortfid') and hasattr(file, 'shortfid') and
+ f.shortfid == file.shortfid):
raise SwError(SW["ERR_FILEEXISTS"])
self.content.append(file)
def select(self, attribute, value, reference=REF["IDENTIFIER_FIRST"],
- index_current=0):
+ index_current=0):
"""
Returns the first file of the DF, that has the 'attribute' with the
specified 'value'. For partial DF name selection you must specify the
@@ -469,15 +487,18 @@ class DF(File):
for i in indexes:
file = self.content[i]
- if (hasattr(file, attribute) and ((getattr(file, attribute)==value)
- or (attribute == 'dfname' and getattr(file,
- attribute).startswith(value)))):
+ if (hasattr(file, attribute) and
+ ((getattr(file, attribute) == value) or
+ (attribute == 'dfname' and
+ getattr(file, attribute).startswith(value)))):
return file
# not found
if isinstance(value, int):
- logging.debug("file (%s=%x) not found in:\n%s" % (attribute, value, self))
+ logging.debug("file (%s=%x) not found in:\n%s" %
+ (attribute, value, self))
elif isinstance(value, str):
- logging.debug("file (%s=%r) not found in:\n%s" % (attribute, value, self))
+ logging.debug("file (%s=%r) not found in:\n%s" %
+ (attribute, value, self))
raise SwError(SW["ERR_FILENOTFOUND"])
def remove(self, file):
@@ -485,26 +506,29 @@ class DF(File):
self.content.remove(file)
-
-class MF(DF):
+class MF(DF):
"""Class for a master file"""
- current = make_property("current", "the currently selected file")
- firstSFT = make_property("firstSFT", "string of length 1. The first software function table from the historical bytes.")
- secondSFT = make_property("secondSFT", "string of length 1. The second software function table from the historical bytes.")
- def __init__(self, filedescriptor=FDB["NOTSHAREABLEFILE"]|FDB["DF"],
- lifecycle=LCB["ACTIVATED"],
- simpletlv_data=None, bertlv_data=None, dfname=None):
+ current = make_property("current", "the currently selected file")
+ firstSFT = make_property("firstSFT", "string of length 1. The first"
+ "software function table from the"
+ "historical bytes.")
+ secondSFT = make_property("secondSFT", "string of length 1. The second"
+ "software function table from the"
+ "historical bytes.")
+
+ def __init__(self, filedescriptor=FDB["NOTSHAREABLEFILE"] | FDB["DF"],
+ lifecycle=LCB["ACTIVATED"],
+ simpletlv_data=None, bertlv_data=None, dfname=None):
"""The file identifier FID["MF"] is automatically added.
See DF for more.
"""
DF.__init__(self, None, FID["MF"], filedescriptor, lifecycle,
- simpletlv_data, bertlv_data, dfname)
- self.current = self
- self.firstSFT = inttostring(MF.makeFirstSoftwareFunctionTable(), 1)
+ simpletlv_data, bertlv_data, dfname)
+ self.current = self
+ self.firstSFT = inttostring(MF.makeFirstSoftwareFunctionTable(), 1)
self.secondSFT = inttostring(MF.makeSecondSoftwareFunctionTable(), 1)
-
@staticmethod
def makeFirstSoftwareFunctionTable(
DFSelectionByFullDFName=True, DFSelectionByPartialDFName=True,
@@ -533,24 +557,22 @@ class MF(DF):
if RecordIdentifierSupported:
fsft |= 1
return fsft
-
@staticmethod
- def makeSecondSoftwareFunctionTable(DCB=DCB["ONETIMEWRITE"]|1):
+ def makeSecondSoftwareFunctionTable(DCB=DCB["ONETIMEWRITE"] | 1):
"""
The second software function table from the historical bytes contains
the data coding byte.
"""
return DCB
-
-
+
def currentDF(self):
"""Returns the current DF."""
if isinstance(self.current, EF):
return self.current.parent
else:
return self.current
-
+
def currentEF(self):
"""Returns the current EF or None if not available."""
if isinstance(self.current, EF):
@@ -565,8 +587,8 @@ class MF(DF):
The result is not prepended with tag and length for neither TCP, FMD
nor FCI template.
"""
- fdm = [ chr(TAG["FILEIDENTIFIER"])+"\x02"+inttostring(file.fid, 2),
- chr(TAG["LIFECYCLESTATUS"])+"\x01"+chr(file.lifecycle) ]
+ fdm = [chr(TAG["FILEIDENTIFIER"]) + "\x02" + inttostring(file.fid, 2),
+ chr(TAG["LIFECYCLESTATUS"]) + "\x01" + chr(file.lifecycle)]
fdm.append(file.extra_fci_data)
# TODO filesize and data objects
@@ -579,11 +601,11 @@ class MF(DF):
if isinstance(file, TransparentStructureEF):
l = inttostring(len(file.data))
fdm.append("%c%c%s" % (TAG["BYTES_EXCLUDINGSTRUCTURE"],
- chr(len(l)), l))
+ chr(len(l)), l))
fdm.append("%c%c%s" % (TAG["BYTES_INCLUDINGSTRUCTURE"],
- chr(len(l)), l))
+ chr(len(l)), l))
fdm.append("%c\x02%c%c" % (TAG["FILEDISCRIPTORBYTE"],
- file.filedescriptor, file.datacoding))
+ file.filedescriptor, file.datacoding))
elif isinstance(file, RecordStructureEF):
l = 0
@@ -594,21 +616,23 @@ class MF(DF):
else:
l += len(r.data)
fdm.append("%c\x02%s" % (TAG["BYTES_EXCLUDINGSTRUCTURE"],
- inttostring(l, 2)))
+ inttostring(l, 2)))
fdm.append("%c\x02%s" % (TAG["BYTES_INCLUDINGSTRUCTURE"],
- inttostring(l, 2)))
+ inttostring(l, 2)))
l = len(records)
fdm.append("%c\x06%c%c%c%c%s" % (TAG["FILEDISCRIPTORBYTE"],
- file.filedescriptor, file.datacoding, file.maxrecordsize >>
- 8, file.maxrecordsize & 0x00ff, inttostring(l, 2)))
+ file.filedescriptor, file.datacoding,
+ file.maxrecordsize >> 8,
+ file.maxrecordsize & 0x00ff,
+ inttostring(l, 2)))
elif isinstance(file, DF):
# TODO number of files == number of data bytes?
fdm.append("%c\x01%c" % (TAG["FILEDISCRIPTORBYTE"],
- file.filedescriptor))
+ file.filedescriptor))
if hasattr(file, 'dfname'):
fdm.append("%c%c%s" % (TAG["DFNAME"], len(file.dfname),
- file.dfname))
+ file.dfname))
else:
raise TypeError
@@ -620,15 +644,15 @@ class MF(DF):
Returns the file specified by 'p1' and 'data' from the select
file command APDU.
"""
- P1_FILE = 0x00
- P1_CHILD_DF = 0x01
- P1_CHILD_EF = 0x02
- P1_PARENT_DF = 0x03
- P1_DF_NAME = 0x04
- P1_PATH_FROM_MF = 0x08
- P1_PATH_FROM_CURRENTDF = 0x09
+ P1_FILE = 0x00
+ P1_CHILD_DF = 0x01
+ P1_CHILD_EF = 0x02
+ P1_PARENT_DF = 0x03
+ P1_DF_NAME = 0x04
+ P1_PATH_FROM_MF = 0x08
+ P1_PATH_FROM_CURRENTDF = 0x09
- if (p1>>4) != 0 or p1 == P1_FILE:
+ if (p1 >> 4) != 0 or p1 == P1_FILE:
# RFU OR
# When P1='00', the card knows either because of a specific coding
# of the file identifier or because of the context of execution of
@@ -657,13 +681,14 @@ class MF(DF):
index_current = -1
else:
index_current = self.content.index(df)
- selected = self.select('dfname', data, p2 &
- REF["REFERENCE_CONTROL_SELECT"], index_current)
+ selected = self.select('dfname', data,
+ p2 & REF["REFERENCE_CONTROL_SELECT"],
+ index_current)
else:
logging.debug("unknown selection method: p1 =%s" % p1)
selected = None
- if selected == None:
+ if selected is None:
raise SwError(SW["ERR_FILENOTFOUND"])
return selected
@@ -671,14 +696,14 @@ class MF(DF):
def selectFile(self, p1, p2, data):
"""
Function for instruction 0xa4. Takes the parameter bytes 'p1', 'p2' as
- integers and 'data' as binary string.
-
+ integers and 'data' as binary string.
+
:returns: the status bytes as two byte long integer and the response
data as binary string.
"""
- P2_FCI = 0
- P2_FCP = 1 << 2
- P2_FMD = 2 << 2
+ P2_FCI = 0
+ P2_FCP = 1 << 2
+ P2_FMD = 2 << 2
P2_NONE = 3 << 2
file = self._selectFile(p1, p2, data)
@@ -703,7 +728,7 @@ class MF(DF):
"""
Decodes 'p1', 'p2' and 'data' from a data unit command (i. e.
read/write/update/search/erase binary) with *even* instruction code.
-
+
:returns: the specified TransparentStructureEF, a list of offsets and a
list of data strings.
"""
@@ -729,7 +754,7 @@ class MF(DF):
"""
Decodes 'p1', 'p2' and 'data' from a data unit command (i. e.
read/write/update/search/erase binary) with *odd* instruction code.
-
+
:returns the specified TransparentStructureEF, a list of offsets and a
list of data strings.
"""
@@ -744,7 +769,7 @@ class MF(DF):
# or response data field, data shall be encapsulated in a
# discretionary data object with tag '53' or '73'.
tlv_data = bertlv_unpack(data)
- offsets = decodeOffsetDataObjects(tlv_data)
+ offsets = decodeOffsetDataObjects(tlv_data)
datalist = decodeDiscretionaryDataObjects(tlv_data)
if p1 == 0 and p2 >> 5 == 0:
@@ -758,8 +783,8 @@ class MF(DF):
def readBinaryPlain(self, p1, p2, data):
"""
Function for instruction 0xb0. Takes the parameter bytes 'p1', 'p2' as
- integers and 'data' as binary string.
-
+ integers and 'data' as binary string.
+
:returns: the status bytes as two byte long integer and the response
data as binary string.
"""
@@ -772,7 +797,7 @@ class MF(DF):
"""
Function for instruction 0xb1. Takes the parameter bytes 'p1', 'p2' as
integers and 'data' as binary string.
-
+
:returns: the status bytes as two byte long integer and the response
data as binary string.
"""
@@ -787,7 +812,7 @@ class MF(DF):
"""
Function for instruction 0xd0. Takes the parameter bytes 'p1', 'p2' as
integers and 'data' as binary string.
-
+
:returns: the status bytes as two byte long integer and the response
data as binary string.
"""
@@ -811,7 +836,7 @@ class MF(DF):
"""
Function for instruction 0xd6. Takes the parameter bytes 'p1', 'p2' as
integers and 'data' as binary string.
-
+
:returns: the status bytes as two byte long integer and the response
data as binary string.
"""
@@ -824,7 +849,7 @@ class MF(DF):
"""
Function for instruction 0xd7. Takes the parameter bytes 'p1', 'p2' as
integers and 'data' as binary string.
-
+
:returns: the status bytes as two byte long integer and the response
data as binary string.
"""
@@ -859,8 +884,9 @@ class MF(DF):
"""
Function for instruction 0x0e. Takes the parameter bytes 'p1', 'p2' as
integers and 'data' as binary string.
-
- :returns: the status bytes as two byte long integer and the response data as binary string.
+
+ :returns: the status bytes as two byte long integer and the response
+ data as binary string.
"""
ef, offsets, datalist = self.dataUnitsDecodePlain(p1, p2, data)
# If INS = '0E', then, if present, the command data field encodes
@@ -882,7 +908,7 @@ class MF(DF):
"""
Function for instruction 0x0f. Takes the parameter bytes 'p1', 'p2' as
integers and 'data' as binary string.
-
+
:returns: the status bytes as two byte long integer and the response
data as binary string.
"""
@@ -912,9 +938,9 @@ class MF(DF):
"""
Decodes 'p1' and 'p2' from a record handling command (i. e.
read/write/update/append/search/erase record).
-
+
:returns: the specified RecordStructureEF, the number or identifier of
- the record and a reference, that specifies which record to select
+ the record and a reference, that specifies which record to select
(i. e. the last 3 bits of 'p1').
"""
if p1 == 0xff:
@@ -926,7 +952,7 @@ class MF(DF):
shortfid = p2 >> 3
if shortfid == 0:
ef = self.currentEF()
- if ef == None:
+ if ef is None:
raise SwError(SW["ERR_NOCURRENTEF"])
elif shortfid == 0x1f:
# RFU
@@ -942,7 +968,7 @@ class MF(DF):
"""
Function for instruction 0xb2. Takes the parameter bytes 'p1', 'p2' as
integers and 'data' as binary string.
-
+
:returns: the status bytes as two byte long integer and the response
data as binary string.
"""
@@ -959,7 +985,7 @@ class MF(DF):
"""
Function for instruction 0xb3. Takes the parameter bytes 'p1', 'p2' as
integers and 'data' as binary string.
-
+
:returns: the status bytes as two byte long integer and the response
data as binary string.
"""
@@ -972,14 +998,16 @@ class MF(DF):
"""
Function for instruction 0xd2. Takes the parameter bytes 'p1', 'p2' as
integers and 'data' as binary string.
-
+
:returns: the status bytes as two byte long integer and the response
data as binary string.
"""
ef, num_id, reference = self.recordHandlingDecode(p1, p2)
- if reference not in [ REF["IDENTIFIER_FIRST"], REF["IDENTIFIER_LAST"],
- REF["IDENTIFIER_NEXT"], REF["IDENTIFIER_PREVIOUS"],
- REF["NUMBER"]]:
+ if reference not in [REF["IDENTIFIER_FIRST"],
+ REF["IDENTIFIER_LAST"],
+ REF["IDENTIFIER_NEXT"],
+ REF["IDENTIFIER_PREVIOUS"],
+ REF["NUMBER"]]:
# RFU
raise SwError(SW["ERR_INCORRECTPARAMETERS"])
ef.writerecord(num_id, reference, 1, data)
@@ -990,14 +1018,16 @@ class MF(DF):
"""
Function for instruction 0xdc. Takes the parameter bytes 'p1', 'p2' as
integers and 'data' as binary string.
-
+
:returns: the status bytes as two byte long integer and the response
data as binary string.
"""
ef, num_id, reference = self.recordHandlingDecode(p1, p2)
- if reference not in [ REF["IDENTIFIER_FIRST"], REF["IDENTIFIER_LAST"],
- REF["IDENTIFIER_NEXT"], REF["IDENTIFIER_PREVIOUS"],
- REF["NUMBER"]]:
+ if reference not in [REF["IDENTIFIER_FIRST"],
+ REF["IDENTIFIER_LAST"],
+ REF["IDENTIFIER_NEXT"],
+ REF["IDENTIFIER_PREVIOUS"],
+ REF["NUMBER"]]:
# RFU
raise SwError(SW["ERR_INCORRECTPARAMETERS"])
ef.updaterecord(num_id, reference, 0, data)
@@ -1008,46 +1038,52 @@ class MF(DF):
"""
Function for instruction 0xdd. Takes the parameter bytes 'p1', 'p2' as
integers and 'data' as binary string.
-
+
:returns: the status bytes as two byte long integer and the response
data as binary string.
"""
ef, num_id, reference = self.recordHandlingDecode(p1, p2)
P2_REPLACE = 0x04
- P2_AND = 0x05
- P2_OR = 0x06
- #P2_XOR = 0x07
+ P2_AND = 0x05
+ P2_OR = 0x06
+ # P2_XOR = 0x07
tlv_data = bertlv_unpack(data)
- if reference in [ REF["IDENTIFIER_FIRST"], REF["IDENTIFIER_LAST"],
- REF["IDENTIFIER_NEXT"], REF["IDENTIFIER_PREVIOUS"]]:
+ if reference in [REF["IDENTIFIER_FIRST"],
+ REF["IDENTIFIER_LAST"],
+ REF["IDENTIFIER_NEXT"],
+ REF["IDENTIFIER_PREVIOUS"]]:
# RFU
raise SwError(SW["ERR_INCORRECTPARAMETERS"])
elif reference == P2_REPLACE:
- ef.writerecord(num_id, reference, decodeOffsetDataObjects(tlv_data)[0],
- decodeDiscretionaryDataObjects(tlv_data)[0],
- DCB["ONETIMEWRITE"])
+ ef.writerecord(num_id, reference,
+ decodeOffsetDataObjects(tlv_data)[0],
+ decodeDiscretionaryDataObjects(tlv_data)[0],
+ DCB["ONETIMEWRITE"])
elif reference == P2_AND:
- ef.writerecord(num_id, reference, decodeOffsetDataObjects(tlv_data)[0],
- decodeDiscretionaryDataObjects(tlv_data)[0],
- DCB["WRITEAND"])
+ ef.writerecord(num_id, reference,
+ decodeOffsetDataObjects(tlv_data)[0],
+ decodeDiscretionaryDataObjects(tlv_data)[0],
+ DCB["WRITEAND"])
elif reference == P2_OR:
- ef.writerecord(num_id, reference, decodeOffsetDataObjects(tlv_data)[0],
- decodeDiscretionaryDataObjects(tlv_data)[0],
- DCB["WRITEOR"])
+ ef.writerecord(num_id, reference,
+ decodeOffsetDataObjects(tlv_data)[0],
+ decodeDiscretionaryDataObjects(tlv_data)[0],
+ DCB["WRITEOR"])
else:
# reference == P2_XOR:
- ef.writerecord(num_id, reference, decodeOffsetDataObjects(tlv_data)[0],
- decodeDiscretionaryDataObjects(tlv_data)[0],
- DCB["PROPRIETARY"])
+ ef.writerecord(num_id, reference,
+ decodeOffsetDataObjects(tlv_data)[0],
+ decodeDiscretionaryDataObjects(tlv_data)[0],
+ DCB["PROPRIETARY"])
return SW["NORMAL"], ""
def appendRecord(self, p1, p2, data):
"""
Function for instruction 0xe2. Takes the parameter bytes 'p1', 'p2' as
- integers and 'data' as binary string.
-
+ integers and 'data' as binary string.
+
:returns: the status bytes as two byte long integer and the response
data as binary string.
"""
@@ -1062,7 +1098,7 @@ class MF(DF):
"""
Function for instruction 0x0c. Takes the parameter bytes 'p1', 'p2' as
integers and 'data' as binary string.
-
+
:returns: the status bytes as two byte long integer and the response
data as binary string.
"""
@@ -1080,22 +1116,22 @@ class MF(DF):
"""
Decodes 'p1', 'p2' and 'data' from a data object handling command (i.
e. get/put data) with *even* instruction code.
-
+
:returns: the specified file, True if the following list regards
SIMPLE-TLV data objects False otherwise and a list of
(tag, length, value)-tuples.
"""
- if self.current == None:
+ if self.current is None:
raise SwError(SW["ERR_NOCURRENTEF"])
file = self.current
- if (p1 == 0 and 0x40 <= p2 and p2 <= 0xfe) or (0x40 <= p1 and p2 != 0
- and p2 != 0xff):
+ if ((p1 == 0 and 0x40 <= p2 and p2 <= 0xfe) or
+ (0x40 <= p1 and p2 != 0 and p2 != 0xff)):
# If bit 1 of INS is set to 0 and P1 to '00', then P2 from '40' to
# 'FE' shall be a BER-TLV tag on a single byte. OR
# If bit 1 of INS is set to 0 and if P1-P2 lies from '4000' to
# 'FFFF', then they shall be a BER-TLV tag on two bytes.
- tlv_data = [((p1<<8) + p2, len(data), data)]
+ tlv_data = [((p1 << 8) + p2, len(data), data)]
isSimpleTlv = False
elif p1 == 0x02 and 0x01 <= p2 and p2 <= 0xfe:
# If bit 1 of INS is set to 0 and P1 to '02', then P2 from '01' to
@@ -1129,8 +1165,8 @@ class MF(DF):
"""
Decodes 'p1', 'p2' and 'data' from a data object handling command (i.
e. get/put data) with *odd* instruction code.
-
- :returns: the specified file, True if the following list regards
+
+ :returns: the specified file, True if the following list regards
SIMPLE-TLV data objects False otherwise and a list of
(tag, length, value)-tuples.
"""
@@ -1141,10 +1177,11 @@ class MF(DF):
# data field provides a file reference data object (tag '51', see
# 5.3.1.2) for identifying a file.
file = getfile_byrefdataobj(self,
- tlv_find_tag(tlv_data, TAG["FILE_REFERENCE"])[0])
- if file == None:
+ tlv_find_tag(tlv_data,
+ TAG["FILE_REFERENCE"])[0])
+ if file is None:
file = self.currentEF()
- if file == None:
+ if file is None:
raise SwError(SW["ERR_NOCURRENTEF"])
elif p1 == 0 and (p2 >> 5) == 0:
# If the first eleven bits of P1-P2 are set to 0 and if bits 5 to 1
@@ -1157,9 +1194,9 @@ class MF(DF):
file = self.currentDF()
else:
# Otherwise, P1-P2 is a file identifier.
- file = self.currentDF().select('fid', p1<<8 + p2)
+ file = self.currentDF().select('fid', p1 << 8 + p2)
- if file == None:
+ if file is None:
raise SwError(SW["ERR_FILENOTFOUND"])
self.current = file
@@ -1169,14 +1206,17 @@ class MF(DF):
"""
Function for instruction 0xca. Takes the parameter bytes 'p1', 'p2' as
integers and 'data' as binary string.
-
+
:returns: the status bytes as two byte long integer and the response
data as binary string.
"""
- file, isSimpleTlv, tlvlist = self.dataObjectHandlingDecodePlain(p1, p2, data)
+ file, isSimpleTlv, tlvlist = self.dataObjectHandlingDecodePlain(p1,
+ p2,
+ data)
# TODO oversized answers
if len(tlvlist) > 0:
- return SW["NORMAL"], file.getdata(isSimpleTlv, [(tlvlist[0][0], 0)])
+ return SW["NORMAL"], file.getdata(isSimpleTlv,
+ [(tlvlist[0][0], 0)])
else:
return SW["NORMAL"], file.getdata(isSimpleTlv, [])
@@ -1184,11 +1224,13 @@ class MF(DF):
"""
Function for instruction 0xcb. Takes the parameter bytes 'p1', 'p2' as
integers and 'data' as binary string.
-
+
:returns: the status bytes as two byte long integer and the response
data as binary string.
"""
- file, tlv_data = self.dataObjectHandlingDecodeEncapsulated(p1, p2, data)
+ file, tlv_data = self.dataObjectHandlingDecodeEncapsulated(p1,
+ p2,
+ data)
# TODO oversized answers
requestedTL = decodeTagList(tlv_data)
if requestedTL == []:
@@ -1202,11 +1244,13 @@ class MF(DF):
"""
Function for instruction 0xda. Takes the parameter bytes 'p1', 'p2' as
integers and 'data' as binary string.
-
+
:returns: the status bytes as two byte long integer and the response
data as binary string.
"""
- file, isSimpleTlv, tlvlist = self.dataObjectHandlingDecodePlain(p1, p2, data)
+ file, isSimpleTlv, tlvlist = self.dataObjectHandlingDecodePlain(p1,
+ p2,
+ data)
file.putdata(isSimpleTlv, tlvlist)
return SW["NORMAL"], ""
@@ -1215,7 +1259,7 @@ class MF(DF):
"""
Function for instruction 0xdb. Takes the parameter bytes 'p1', 'p2' as
integers and 'data' as binary string.
-
+
:returns: the status bytes as two byte long integer and the response
data as binary string.
"""
@@ -1224,7 +1268,6 @@ class MF(DF):
return SW["NORMAL"], ""
-
@staticmethod
def create(p1, p2, data):
"""
@@ -1237,45 +1280,54 @@ class MF(DF):
# TODO number of records on one or two bytes
raise SwError(SW["ERR_NOTSUPPORTED"])
if l >= 3:
- args["maxrecordsize"] = stringtoint(value[2:])
+ args["maxrecordsize"] = stringtoint(value[2:])
if l >= 2:
- args["datacoding"] = ord(value[1])
+ args["datacoding"] = ord(value[1])
if l >= 1:
args["filedescriptor"] = ord(value[0])
+
def shortfid2args(value, args):
s = stringtoint(value)
if (s & 7) == 0:
shortfid = s >> 3
if shortfid != 0:
args["shortfid"] = shortfid
+
def unknown(tag, value):
logging.debug("unknown tag 0x%x with %r" % (tag, value))
+
tag2cmd = {
# TODO support other tags
- TAG["FILEDISCRIPTORBYTE"] : 'fdb2args(value, args)',
- TAG["FILEIDENTIFIER"] : 'args["fid"] = stringtoint(value)',
- TAG["DFNAME"] : 'args["dfname"] = value',
- TAG["SHORTFID"] : 'shortfid2args(value, args)',
- TAG["LIFECYCLESTATUS"] : 'args["lifecycle"] = stringtoint(value)',
- TAG["BYTES_EXCLUDINGSTRUCTURE"] : 'args["data"] = chr(0)*stringtoint(value)',
- TAG["BYTES_INCLUDINGSTRUCTURE"] : 'args["data"] = chr(0)*stringtoint(value)',
+ TAG["FILEDISCRIPTORBYTE"]: 'fdb2args(value, args)',
+ TAG["FILEIDENTIFIER"]: 'args["fid"] = stringtoint(value)',
+ TAG["DFNAME"]: 'args["dfname"] = value',
+ TAG["SHORTFID"]: 'shortfid2args(value, args)',
+ TAG["LIFECYCLESTATUS"]: 'args["lifecycle"] = ' + \
+ 'stringtoint(value)',
+ TAG["BYTES_EXCLUDINGSTRUCTURE"]: 'args["data"] = chr(0) ' + \
+ '* stringtoint(value)',
+ TAG["BYTES_INCLUDINGSTRUCTURE"]: 'args["data"] = chr(0) ' + \
+ '* stringtoint(value)',
}
- fcp_list = tlv_find_tags( bertlv_unpack(data),
- [TAG["FILECONTROLINFORMATION"], TAG["FILECONTROLPARAMETERS"]])
+ fcp_list = tlv_find_tags(bertlv_unpack(data),
+ [TAG["FILECONTROLINFORMATION"],
+ TAG["FILECONTROLPARAMETERS"]])
if not fcp_list:
raise SwError(SW["ERR_INCORRECTPARAMETERS"])
files = []
- args = { "parent": None }
+ args = {"parent": None}
if p1 != 0:
args["filedescriptor"] = p1
if (p2 >> 3) != 0:
args["shortfid"] = p2 >> 3
for T, _, tlv_data in fcp_list:
- if T != TAG["FILECONTROLPARAMETERS"] and T != TAG["FILECONTROLINFORMATION"]:
+ if (T != TAG["FILECONTROLPARAMETERS"] and
+ T != TAG["FILECONTROLINFORMATION"]):
raise ValueError
for tag, _, value in tlv_data:
- exec tag2cmd.get(tag, 'unknown(tag, value)') in locals(), globals()
+ exec tag2cmd.get(tag, 'unknown(tag, value)') in locals(),\
+ globals()
if (args["filedescriptor"] & FDB["DF"]) == FDB["DF"]:
# FIXME: data for DF
@@ -1287,8 +1339,8 @@ class MF(DF):
[FDB["EFSTRUCTURE_NOINFORMATIONGIVEN"],
FDB["EFSTRUCTURE_TRANSPARENT"]]):
file = TransparentStructureEF(**args)
- file.writebinary( decodeOffsetDataObjects(tlv_data),
- decodeDiscretionaryDataObjects(tlv_data) )
+ file.writebinary(decodeOffsetDataObjects(tlv_data),
+ decodeDiscretionaryDataObjects(tlv_data))
else:
file = RecordStructureEF(**args)
@@ -1300,12 +1352,12 @@ class MF(DF):
"""
Function for instruction 0xe0. Takes the parameter bytes 'p1', 'p2' as
integers and 'data' as binary string.
-
+
:returns: the status bytes as two byte long integer and the response
data as binary string.
"""
df = self.currentDF()
- if df == None:
+ if df is None:
raise SwError(SW["ERR_NOCURRENTEF"])
for file in self.create(p1, p2, data):
@@ -1319,26 +1371,28 @@ class MF(DF):
"""
Function for instruction 0xe4. Takes the parameter bytes 'p1', 'p2' as
integers and 'data' as binary string.
-
+
:returns: the status bytes as two byte long integer and the response
data as binary string.
"""
file = self._selectFile(p1, p2, data)
file.parent.content.remove(file)
- # FIXME: free memory of file and remove its content from the security device
+ # FIXME: free memory of file and remove its content from the security
+ # device
return SW["NORMAL"], ""
-
-class EF(File):
+class EF(File):
"""Template class for an elementary file."""
- shortfid = make_property("shortfid", "integer with 1<=shortfid<=30. Short EF identifier.")
+ shortfid = make_property("shortfid", "integer with 1<=shortfid<=30."
+ "Short EF identifier.")
datacoding = make_property("datacoding", "integer. Data coding byte.")
+
def __init__(self, parent, fid, filedescriptor,
- lifecycle=LCB["ACTIVATED"],
- simpletlv_data=None, bertlv_data=None,
- datacoding=DCB["ONETIMEWRITE"], shortfid=0):
+ lifecycle=LCB["ACTIVATED"],
+ simpletlv_data=None, bertlv_data=None,
+ datacoding=DCB["ONETIMEWRITE"], shortfid=0):
"""
The constructor is supposed to be involved creation of a by creation of
a TransparentStructureEF or RecordStructureEF.
@@ -1353,26 +1407,27 @@ class EF(File):
raise SwError(SW["ERR_INCORRECTPARAMETERS"])
self.shortfid = shortfid
File.__init__(self, parent, fid, filedescriptor, lifecycle,
- simpletlv_data,
- bertlv_data)
+ simpletlv_data,
+ bertlv_data)
self.datacoding = datacoding
-
-class TransparentStructureEF(EF):
+class TransparentStructureEF(EF):
"""Class for an elementary file with transparent structure."""
data = make_property("data", "string (encrypted). The file's data.")
- def __init__(self, parent, fid, filedescriptor=FDB["EFSTRUCTURE_TRANSPARENT"],
- lifecycle=LCB["ACTIVATED"],
- simpletlv_data=None, bertlv_data=None,
- datacoding=DCB["ONETIMEWRITE"], shortfid=0, data=""):
+
+ def __init__(self, parent, fid,
+ filedescriptor=FDB["EFSTRUCTURE_TRANSPARENT"],
+ lifecycle=LCB["ACTIVATED"],
+ simpletlv_data=None, bertlv_data=None,
+ datacoding=DCB["ONETIMEWRITE"], shortfid=0, data=""):
"""
See EF for more.
"""
EF.__init__(self, parent, fid,
- filedescriptor, lifecycle,
- simpletlv_data, bertlv_data,
- datacoding, shortfid)
+ filedescriptor, lifecycle,
+ simpletlv_data, bertlv_data,
+ datacoding, shortfid)
self.data = data
def readbinary(self, offset):
@@ -1405,7 +1460,8 @@ class TransparentStructureEF(EF):
def updatebinary(self, offsets, datalist):
"""
- x.updatebinary(offsets, datalist) <==> x.writebinary(offsets, datalist, DCB["ONETIMEWRITE"])
+ x.updatebinary(offsets, datalist) <==>
+ x.writebinary(offsets, datalist, DCB["ONETIMEWRITE"])
"""
return self.writebinary(offsets, datalist, DCB["ONETIMEWRITE"])
@@ -1415,9 +1471,9 @@ class TransparentStructureEF(EF):
sequentially starting from 'erasefrom' ending at 'eraseto'.
"""
data = self.data
- if erasefrom == None:
+ if erasefrom is None:
erasefrom = 0
- if eraseto == None:
+ if eraseto is None:
eraseto = len(data)
if erasefrom > len(data):
@@ -1429,10 +1485,10 @@ class TransparentStructureEF(EF):
self.data = data
-
-class Record(object):
- data = make_property("data", "string. The record's data.")
- identifier = make_property("identifier", "integer with 1<= identifier< = 0xfe. The record's identifier.")
+class Record(object):
+ data = make_property("data", "string. The record's data.")
+ identifier = make_property("identifier", "integer with 1 <= identifier <="
+ " 0xfe. The record's identifier.")
"""Class for a Record of an elementary of record structure"""
def __init__(self, identifier=None, data=""):
"""
@@ -1450,17 +1506,20 @@ class Record(object):
__repr__ = __str__
-
-class RecordStructureEF(EF):
+class RecordStructureEF(EF):
"""Class for an elementary file with record structure."""
- records = make_property("records", "list of records (encrypted)")
- maxrecordsize = make_property("maxrecordsize", "integer. maximum length of a record's data.")
- recordpointer = make_property("recordpointer", "integer. Points to the current record (i. e. index of records).")
+ records = make_property("records", "list of records (encrypted)")
+ maxrecordsize = make_property("maxrecordsize", "integer. maximum length of"
+ " a record's data.")
+ recordpointer = make_property("recordpointer", "integer. Points to the "
+ "current record (i. e. "
+ "index of records).")
+
def __init__(self, parent, fid, filedescriptor,
- lifecycle=LCB["ACTIVATED"],
- simpletlv_data=None,
- bertlv_data=None, datacoding=DCB["ONETIMEWRITE"], shortfid=0,
- maxrecordsize=0xffff, records=[]):
+ lifecycle=LCB["ACTIVATED"],
+ simpletlv_data=None,
+ bertlv_data=None, datacoding=DCB["ONETIMEWRITE"], shortfid=0,
+ maxrecordsize=0xffff, records=[]):
"""
You should specify the appropriate file descriptor byte to specify
which kind of record structured file you want to create (i. e.
@@ -1474,11 +1533,11 @@ class RecordStructureEF(EF):
if not isinstance(records, list):
raise TypeError("must be a list of Records")
EF.__init__(self, parent, fid, filedescriptor, lifecycle,
- simpletlv_data, bertlv_data,
- datacoding, shortfid)
+ simpletlv_data, bertlv_data,
+ datacoding, shortfid)
for r in records:
- if len(r.data) > maxrecordsize or (self.hasFixedRecordSize() and
- len(r.data) < maxrecordsize):
+ if (len(r.data) > maxrecordsize or (self.hasFixedRecordSize() and
+ len(r.data) < maxrecordsize)):
raise SwError(SW["ERR_INCOMPATIBLEWITHFILE"])
self.records = records
self.resetRecordPointer()
@@ -1491,8 +1550,8 @@ class RecordStructureEF(EF):
def isCyclic(self):
"""Returns True if the EF is of cyclic structure, False otherwise."""
attr = self.filedescriptor & 0x07
- if (attr==FDB["EFSTRUCTURE_CYCLIC_NOFURTHERINFO"] or
- attr==FDB["EFSTRUCTURE_CYCLIC_SIMPLETLV"]):
+ if (attr == FDB["EFSTRUCTURE_CYCLIC_NOFURTHERINFO"] or
+ attr == FDB["EFSTRUCTURE_CYCLIC_SIMPLETLV"]):
return True
else:
return False
@@ -1500,11 +1559,12 @@ class RecordStructureEF(EF):
def hasSimpleTlv(self):
"""Returns True if the EF is of TLV structure, False otherwise."""
attr = self.filedescriptor & 0x03
- if (attr==FDB["EFSTRUCTURE_LINEAR_FIXED_SIMPLETLV"] or
- attr==FDB["EFSTRUCTURE_LINEAR_VARIABLESIMPLETLV"] or
- attr==FDB["EFSTRUCTURE_LINEAR_VARIABLESIMPLETLV"]):
+ if (attr == FDB["EFSTRUCTURE_LINEAR_FIXED_SIMPLETLV"] or
+ attr == FDB["EFSTRUCTURE_LINEAR_VARIABLESIMPLETLV"] or
+ attr == FDB["EFSTRUCTURE_LINEAR_VARIABLESIMPLETLV"]):
return True
- else: return False
+ else:
+ return False
def hasFixedRecordSize(self):
"""Returns True if the records are of fixed size, False otherwise."""
@@ -1520,7 +1580,8 @@ class RecordStructureEF(EF):
Returns a list of records.
:param num_id: The requested record's number or identifier
- :param reference: Specifies which record to select (usually the last 3 bits of 'p1' of a record handling command)
+ :param reference: Specifies which record to select (usually the last 3
+ bits of 'p1' of a record handling command)
"""
if (reference >> 2) == 1:
return self.__getRecordsByNumber(num_id, reference)
@@ -1532,10 +1593,10 @@ class RecordStructureEF(EF):
Returns a list of records. Is to be involved by __getRecords.
:param number: The requested record's number
- :param reference: Specifies which record to select (usually the last 3
+ :param reference: Specifies which record to select (usually the last 3
bits of 'p1' of a record handling command)
"""
- result = []
+ result = []
records = self.records
if number == 0:
@@ -1569,12 +1630,12 @@ class RecordStructureEF(EF):
:param reference: Specifies which record to select (usually the last 3
bits of 'p1' of a record handling command)
"""
- result = []
+ result = []
records = self.records
indexes = get_indexes(records, reference, self.recordpointer)
for i in indexes:
- if (not self.hasSimpleTlv()) or records[i].identifier==id:
+ if (not self.hasSimpleTlv()) or records[i].identifier == id:
if result == []:
self.recordpointer = i
result.append(records[i])
@@ -1590,8 +1651,8 @@ class RecordStructureEF(EF):
Returns a data string from the given 'offset'. 'num_id' and 'reference'
specify the record (see __getRecords).
"""
- records = self.__getRecords(num_id, reference)
- result = []
+ records = self.__getRecords(num_id, reference)
+ result = []
for r in records:
if offset == 0:
result.append(r.data)
@@ -1605,7 +1666,7 @@ class RecordStructureEF(EF):
return result
def writerecord(self, num_id, reference, offset, data,
- datacoding=None):
+ datacoding=None):
"""
Writes a data string to the 'offset' of a record using the given data
coding byte. 'num_id' and 'reference' specify the record (see
@@ -1618,10 +1679,10 @@ class RecordStructureEF(EF):
if datacoding:
records[0].data = write(records[0].data, [data], [0], datacoding,
- self.maxrecordsize)
+ self.maxrecordsize)
else:
records[0].data = write(records[0].data, [data], [0],
- self.datacoding, self.maxrecordsize)
+ self.datacoding, self.maxrecordsize)
if self.hasSimpleTlv():
# identifier/tag could have changed
@@ -1629,9 +1690,11 @@ class RecordStructureEF(EF):
def updaterecord(self, num_id, reference, offset, data):
"""
- x.updaterecord(num_id, reference, offset, data) <==> x.writerecord(num_id, reference, offset, data, DCB["ONETIMEWRITE"])
+ x.updaterecord(num_id, reference, offset, data) <==>
+ x.writerecord(num_id, reference, offset, data, DCB["ONETIMEWRITE"])
"""
- return self.writerecord(num_id, reference, offset, data, DCB["ONETIMEWRITE"])
+ return self.writerecord(num_id, reference, offset, data,
+ DCB["ONETIMEWRITE"])
def appendrecord(self, data):
"""
@@ -1669,4 +1732,3 @@ class RecordStructureEF(EF):
r.data = ""
r.identifier = None
return SW["NORMAL"]
-
diff --git a/virtualsmartcard/src/vpicc/virtualsmartcard/SmartcardSAM.py b/virtualsmartcard/src/vpicc/virtualsmartcard/SmartcardSAM.py
index 4670951..875154b 100644
--- a/virtualsmartcard/src/vpicc/virtualsmartcard/SmartcardSAM.py
+++ b/virtualsmartcard/src/vpicc/virtualsmartcard/SmartcardSAM.py
@@ -26,6 +26,7 @@ from virtualsmartcard.SWutils import SwError, SW
from virtualsmartcard.utils import inttostring, stringtoint, hexdump
from virtualsmartcard.SEutils import Security_Environment
+
def get_referenced_cipher(p1):
"""
P1 defines the algorithm and mode to use. We dispatch it and return a
@@ -44,43 +45,45 @@ def get_referenced_cipher(p1):
0x08: "DSA"
}
- if (ciphertable.has_key(p1)):
+ if (p1 in ciphertable):
return ciphertable[p1]
else:
raise SwError(SW["ERR_INCORRECTP1P2"])
-
+
+
class SAM(object):
"""
This class is used to store the data needed by the SAM.
- It includes the PIN, the master key of the SAM and a hashmap containing all
+ It includes the PIN, the master key of the SAM and a hashmap containing all
the keys used by the file encryption system. The keys in the hashmap are
indexed via the path to the corresponding container.
"""
- def __init__(self, PIN, cardNumber, mf=None, cardSecret=None, default_se=Security_Environment):
+ def __init__(self, PIN, cardNumber, mf=None, cardSecret=None,
+ default_se=Security_Environment):
self.PIN = PIN
self.mf = mf
self.cardNumber = cardNumber
- self.last_challenge = None #Will contain non-readable binary string
- self.counter = 3 #Number of tries for PIN validation
+ self.last_challenge = None # Will contain non-readable binary string
+ self.counter = 3 # Number of tries for PIN validation
self.cipher = 0x01
self.asym_key = None
-
+
keylen = vsCrypto.get_cipher_keylen(get_referenced_cipher(self.cipher))
- if cardSecret is None: #Generate a random card secret
+ if cardSecret is None: # Generate a random card secret
self.cardSecret = urandom(keylen)
else:
if len(cardSecret) != keylen:
- raise ValueError("cardSecret has the wrong key length for: " +\
- get_referenced_cipher(self.cipher))
+ raise ValueError("cardSecret has the wrong key length for: " +
+ get_referenced_cipher(self.cipher))
else:
- self.cardSecret = cardSecret
+ self.cardSecret = cardSecret
- #Security Environments may be saved to/retrieved from this dictionary
- self.saved_SEs = {}
+ # Security Environments may be saved to/retrieved from this dictionary
+ self.saved_SEs = {}
self.default_se = default_se
self.current_SE = default_se(self.mf, self)
@@ -90,7 +93,7 @@ class SAM(object):
needs a reference to the filesystem in order to store/retrieve keys.
"""
self.mf = mf
-
+
def FSencrypt(self, data):
"""
Encrypt the given data, using the parameters stored in the SAM.
@@ -106,23 +109,23 @@ class SAM(object):
might not be added in a future version.
"""
return data
-
+
def store_SE(self, SEID):
"""
- Stores the current Security environment in the secure access module. The
- SEID is used as a reference to identify the SE.
+ Stores the current Security environment in the secure access module.
+ The SEID is used as a reference to identify the SE.
"""
SEstr = dumps(self.current_SE)
self.saved_SEs[SEID] = SEstr
return SW["NORMAL"], ""
-
+
def restore_SE(self, SEID):
"""
- Restores a Security Environment from the SAM and replaces the current SE
- with it
+ Restores a Security Environment from the SAM and replaces the current
+ SE with it.
"""
-
- if (not self.saved_SEs.has_key(SEID)):
+
+ if (SEID not in self.saved_SEs):
raise SwError(SW["ERR_REFNOTUSABLE"])
else:
SEstr = self.saved_SEs[SEID]
@@ -131,45 +134,44 @@ class SAM(object):
self.current_SE = SE
else:
raise SwError(SW["ERR_REFNOTUSABLE"])
-
+
return SW["NORMAL"], ""
-
-
+
def erase_SE(self, SEID):
"""
Erases a Security Environment stored under SEID from the SAM
"""
- if (not self.saved_SEs.has_key(SEID)):
+ if (SEID not in self.saved_SEs):
raise SwError(SW["ERR_REFNOTUSABLE"])
else:
del self.saved_SEs[SEID]
-
+
return SW["NORMAL"], ""
-
+
def set_asym_algorithm(self, cipher, keytype):
"""
- :param cipher: Public/private key object from used for encryption
- :param keytype: Type of the public key (e.g. RSA, DSA)
+ :param cipher: Public/private key object from used for encryption
+ :param keytype: Type of the public key (e.g. RSA, DSA)
"""
- if not keytype in range(0x07, 0x08):
+ if keytype not in range(0x07, 0x08):
raise SwError(SW["ERR_INCORRECTP1P2"])
else:
self.cipher = type
self.asym_key = cipher
-
- def verify(self, p1, p2, PIN):
+
+ def verify(self, p1, p2, PIN):
"""
Authenticate the card user. Check if he entered a valid PIN.
- If the PIN is invalid decrement retry counter. If retry counter
+ If the PIN is invalid decrement retry counter. If retry counter
equals zero, block the card until reset with correct PUK
"""
-
+
logging.debug("Received PIN: %s", PIN.strip())
- PIN = PIN.replace("\0","") #Strip NULL characters
-
+ PIN = PIN.replace("\0", "") # Strip NULL characters
+
if p1 != 0x00:
raise SwError(SW["ERR_INCORRECTP1P2"])
-
+
if self.counter > 0:
if self.PIN == PIN:
self.counter = 3
@@ -184,32 +186,32 @@ class SAM(object):
"""
Change the specified referenced data (e.g. CHV) of the card
"""
-
- data = data.replace("\0","") #Strip NULL characters
+
+ data = data.replace("\0", "") # Strip NULL characters
self.PIN = data
- return SW["NORMAL"], ""
+ return SW["NORMAL"], ""
def internal_authenticate(self, p1, p2, data):
"""
Authenticate card to terminal. Encrypt the challenge of the terminal
to prove key posession
"""
-
- if p1 == 0x00: #No information given
- cipher = get_referenced_cipher(self.cipher)
+
+ if p1 == 0x00: # No information given
+ cipher = get_referenced_cipher(self.cipher)
else:
cipher = get_referenced_cipher(p1)
if cipher == "RSA" or cipher == "DSA":
- crypted_challenge = self.asym_key.sign(data,"")
+ crypted_challenge = self.asym_key.sign(data, "")
crypted_challenge = crypted_challenge[0]
crypted_challenge = inttostring(crypted_challenge)
else:
key = self._get_referenced_key(p1, p2)
crypted_challenge = vsCrypto.encrypt(cipher, key, data)
-
+
return SW["NORMAL"], crypted_challenge
-
+
def external_authenticate(self, p1, p2, data):
"""
Authenticate the terminal to the card. Check whether Terminal correctly
@@ -217,44 +219,44 @@ class SAM(object):
"""
if self.last_challenge is None:
raise SwError(SW["ERR_CONDITIONNOTSATISFIED"])
-
- key = self._get_referenced_key(p1, p2)
- if p1 == 0x00: #No information given
- cipher = get_referenced_cipher(self.cipher)
+
+ key = self._get_referenced_key(p1, p2)
+ if p1 == 0x00: # No information given
+ cipher = get_referenced_cipher(self.cipher)
else:
- cipher = get_referenced_cipher(p1)
-
+ cipher = get_referenced_cipher(p1)
+
blocklen = vsCrypto.get_cipher_blocklen(cipher)
reference = vsCrypto.append_padding(blocklen, self.last_challenge)
reference = vsCrypto.encrypt(cipher, key, reference)
if(reference == data):
- #Invalidate last challenge
- self.last_challenge = None
+ # Invalidate last challenge
+ self.last_challenge is None
return SW["NORMAL"], ""
else:
raise SwError(SW["WARN_NOINFO63"])
- #TODO: Counter for external authenticate?
+ # TODO: Counter for external authenticate?
- def mutual_authenticate(self, p1, p2, mutual_challenge):
+ def mutual_authenticate(self, p1, p2, mutual_challenge):
"""
- Takes an encrypted challenge in the form
+ Takes an encrypted challenge in the form
'Terminal Challenge | Card Challenge | Card number'
and checks it for validity. If the challenge is successful
the card encrypts 'Card Challenge | Terminal challenge' and
returns this value
"""
-
+
key = self._get_referenced_key(p1, p2)
card_number = self.get_card_number()
- if (key == None):
+ if (key is None):
raise SwError(SW["ERR_INCORRECTP1P2"])
- if p1 == 0x00: #No information given
- cipher = get_referenced_cipher(self.cipher)
+ if p1 == 0x00: # No information given
+ cipher = get_referenced_cipher(self.cipher)
else:
cipher = get_referenced_cipher(p1)
-
- if (cipher == None):
+
+ if (cipher is None):
raise SwError(SW["ERR_INCORRECTP1P2"])
plain = vsCrypto.decrypt(cipher, key, mutual_challenge)
@@ -262,74 +264,74 @@ class SAM(object):
terminal_challenge = plain[:last_challenge_len-1]
card_challenge = plain[last_challenge_len:-len(card_number)-1]
serial_number = plain[-len(card_number):]
-
+
if terminal_challenge != self.last_challenge:
raise SwError(SW["WARN_NOINFO63"])
elif serial_number != card_number:
raise SwError(SW["WARN_NOINFO63"])
-
+
result = card_challenge + terminal_challenge
return SW["NORMAL"], vsCrypto.encrypt(cipher, key, result)
-
+
def get_challenge(self, p1, p2, data):
"""
Generate a random number of maximum 8 Byte and return it.
"""
- if (p1 != 0x00 or p2 != 0x00): #RFU
+ if (p1 != 0x00 or p2 != 0x00): # RFU
raise SwError(SW["ERR_INCORRECTP1P2"])
-
- length = 8 #Length of the challenge in Byte
+
+ length = 8 # Length of the challenge in Byte
self.last_challenge = urandom(length)
logging.debug("Generated challenge: %s", self.last_challenge)
return SW["NORMAL"], self.last_challenge
-
+
def get_card_number(self):
return SW["NORMAL"], inttostring(self.cardNumber)
-
+
def _get_referenced_key(self, p1, p2):
"""
This method returns the key specified by the p2 parameter. The key may
be stored on the cards filesystem.
- :param p1: Specifies the algorithm to use. Needed to know the keylength.
- :param p2: Specifies a reference to the key to be used for encryption
+ :param p1: Specifies the algorithm to use.
+ :param p2: Specifies a reference to the key to be used for encryption.
- == == == == == == == == =============================================
+ == == == == == == == == ===========================================
b8 b7 b6 b5 b4 b3 b2 b1 Meaning
- == == == == == == == == =============================================
+ == == == == == == == == ===========================================
0 0 0 0 0 0 0 0 No information is given
0 - - - - - - - Global reference data(e.g. MF specific key)
- 1 - - - - - - - Specific reference data(e.g. DF specific key)
+ 1 - - - - - - - Specific reference data(e.g. DF specific
+ key)
- - - x x x x x Number of the secret
- == == == == == == == == =============================================
+ == == == == == == == == ===========================================
Any other value RFU
"""
-
+
key = None
qualifier = p2 & 0x1F
- algo = get_referenced_cipher(p1)
+ algo = get_referenced_cipher(p1)
keylength = vsCrypto.get_cipher_keylen(algo)
- if (p2 == 0x00): #No information given, use the global card key
+ if (p2 == 0x00): # No information given, use the global card key
key = self.cardSecret
- #We treat global and specific reference data alike
- #elif ((p2 >> 7) == 0x01 or (p2 >> 7) == 0x00):
- else:
- #Interpret qualifier as an short fid (try to read the key from FS)
- if self.mf == None:
+ # We treat global and specific reference data alike
+ else:
+ # Interpret qualifier as an short fid (try to read the key from FS)
+ if self.mf is None:
raise SwError(SW["ERR_REFNOTUSABLE"])
df = self.mf.currentDF()
fid = df.select("fid", stringtoint(qualifier))
key = fid.readbinary(keylength)
- if key != None:
+ if key is not None:
return key
- else:
+ else:
raise SwError(SW["ERR_REFNOTUSABLE"])
-
- #The following commands define the Secure Messaging interface
+
+ # The following commands define the Secure Messaging interface
def generate_public_key_pair(self, p1, p2, data):
return self.current_SE.generate_public_key_pair(p1, p2, data)
@@ -342,17 +344,17 @@ class SAM(object):
return self.current_SE.parse_SM_CAPDU(CAPDU, header_authentication)
except:
raise SwError(SW["ERR_SECMESSOBJECTSINCORRECT"])
-
+
def protect_result(self, sw, unprotected_result):
"""
Protect a plain response APDU by Secure Messaging
"""
- logging.info("Unprotected Response Data:\n"+hexdump(unprotected_result))
+ logging.info("Unprotected Response Data:\n" +
+ hexdump(unprotected_result))
return self.current_SE.protect_response(sw, unprotected_result)
def perform_security_operation(self, p1, p2, data):
return self.current_SE.perform_security_operation(p1, p2, data)
-
+
def manage_security_environment(self, p1, p2, data):
return self.current_SE.manage_security_environment(p1, p2, data)
-
diff --git a/virtualsmartcard/src/vpicc/virtualsmartcard/TLVutils.py b/virtualsmartcard/src/vpicc/virtualsmartcard/TLVutils.py
index 43903f9..f28b95a 100644
--- a/virtualsmartcard/src/vpicc/virtualsmartcard/TLVutils.py
+++ b/virtualsmartcard/src/vpicc/virtualsmartcard/TLVutils.py
@@ -1,18 +1,18 @@
#
# Copyright (C) 2011 Frank Morgner
-#
+#
# This file is part of virtualsmartcard.
-#
+#
# 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
# Software Foundation, either version 3 of the License, or (at your option) any
# later version.
-#
+#
# virtualsmartcard is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
# more details.
-#
+#
# You should have received a copy of the GNU General Public License along with
# virtualsmartcard. If not, see .
#
@@ -20,39 +20,41 @@
from virtualsmartcard.utils import stringtoint
TAG = {}
-TAG["FILECONTROLPARAMETERS"] = 0x62
-TAG["FILEMANAGEMENTDATA"] = 0x64
-TAG["FILECONTROLINFORMATION"] = 0x6F
+TAG["FILECONTROLPARAMETERS"] = 0x62
+TAG["FILEMANAGEMENTDATA"] = 0x64
+TAG["FILECONTROLINFORMATION"] = 0x6F
TAG["BYTES_EXCLUDINGSTRUCTURE"] = 0x80
TAG["BYTES_INCLUDINGSTRUCTURE"] = 0x81
-TAG["FILEDISCRIPTORBYTE"] = 0x82
-TAG["FILEIDENTIFIER"] = 0x83
-TAG["DFNAME"] = 0x84
-TAG["PROPRIETARY_NOTBERTLV"] = 0x85
-TAG["PROPRIETARY_SECURITY"] = 0x86
-TAG["FIDEF_CONTAININGFCI"] = 0x87
-TAG["SHORTFID"] = 0x88
-TAG["LIFECYCLESTATUS"] = 0x8A
-TAG["SA_EXPANDEDFORMAT"] = 0x8B
-TAG["SA_COMPACTFORMAT"] = 0x8C
-TAG["FIDEF_CONTAININGSET"] = 0x8D
-TAG["CHANNELSECURITY"] = 0x8E
-TAG["SA_DATAOBJECTS"] = 0xA0
+TAG["FILEDISCRIPTORBYTE"] = 0x82
+TAG["FILEIDENTIFIER"] = 0x83
+TAG["DFNAME"] = 0x84
+TAG["PROPRIETARY_NOTBERTLV"] = 0x85
+TAG["PROPRIETARY_SECURITY"] = 0x86
+TAG["FIDEF_CONTAININGFCI"] = 0x87
+TAG["SHORTFID"] = 0x88
+TAG["LIFECYCLESTATUS"] = 0x8A
+TAG["SA_EXPANDEDFORMAT"] = 0x8B
+TAG["SA_COMPACTFORMAT"] = 0x8C
+TAG["FIDEF_CONTAININGSET"] = 0x8D
+TAG["CHANNELSECURITY"] = 0x8E
+TAG["SA_DATAOBJECTS"] = 0xA0
TAG["PROPRIETARY_SECURITYTEMP"] = 0xA1
-TAG["PROPRIETARY_BERTLV"] = 0xA5
-TAG["SA_EXPANDEDFORMAT_TEMP"] = 0xAB
-TAG["CRYPTIDENTIFIER_TEMP"] = 0xAC
-TAG["DISCRETIONARY_DATA"] = 0x53
-TAG["DISCRETIONARY_TEMPLATE"] = 0x73
-TAG["OFFSET_DATA"] = 0x54
-TAG["TAG_LIST"] = 0x5C
-TAG["HEADER_LIST"] = 0x5D
-TAG["EXTENDED_HEADER_LIST"] = 0x4D
+TAG["PROPRIETARY_BERTLV"] = 0xA5
+TAG["SA_EXPANDEDFORMAT_TEMP"] = 0xAB
+TAG["CRYPTIDENTIFIER_TEMP"] = 0xAC
+TAG["DISCRETIONARY_DATA"] = 0x53
+TAG["DISCRETIONARY_TEMPLATE"] = 0x73
+TAG["OFFSET_DATA"] = 0x54
+TAG["TAG_LIST"] = 0x5C
+TAG["HEADER_LIST"] = 0x5D
+TAG["EXTENDED_HEADER_LIST"] = 0x4D
-def tlv_unpack(data):
+
+def tlv_unpack(data):
ber_class = (ord(data[0]) & 0xC0) >> 6
- constructed = (ord(data[0]) & 0x20) != 0 ## 0 = primitive, 0x20 = constructed
- tag = ord(data[0])
+ # 0 = primitive, 0x20 = constructed
+ constructed = (ord(data[0]) & 0x20) != 0
+ tag = ord(data[0])
data = data[1:]
if (tag & 0x1F) == 0x1F:
tag = (tag << 8) | ord(data[0])
@@ -60,114 +62,117 @@ def tlv_unpack(data):
data = data[1:]
tag = (tag << 8) | ord(data[0])
data = data[1:]
-
+
length = ord(data[0])
if length < 0x80:
data = data[1:]
elif length & 0x80 == 0x80:
length_ = 0
data = data[1:]
- for i in range(0,length & 0x7F):
+ for i in range(0, length & 0x7F):
length_ = length_ * 256 + ord(data[0])
data = data[1:]
length = length_
-
+
value = data[:length]
rest = data[length:]
-
+
return ber_class, constructed, tag, length, value, rest
-def tlv_find_tags(tlv_data, tags, num_results = None):
+def tlv_find_tags(tlv_data, tags, num_results=None):
"""Find (and return) all instances of tags in the given tlv structure (as
returned by unpack). If num_results is specified then at most that many
results will be returned."""
-
+
results = []
+
def find_recursive(tlv_data):
for d in tlv_data:
- t,l,v = d[:3]
+ t, l, v = d[:3]
if t in tags:
results.append(d)
else:
- if isinstance(v, list): # FIXME Refactor the whole TLV code into a class
+ if isinstance(v, list):
find_recursive(v)
-
+
if num_results is not None and len(results) >= num_results:
return
-
+
find_recursive(tlv_data)
-
+
return results
-def tlv_find_tag(tlv_data, tag, num_results = None):
- """Find (and return) all instances of tag in the given tlv structure (as returned by unpack).
- If num_results is specified then at most that many results will be returned."""
-
+def tlv_find_tag(tlv_data, tag, num_results=None):
+ """Find (and return) all instances of tag in the given tlv structure (as
+ returned by unpack).
+ If num_results is specified then at most that many results will be
+ returned."""
+
return tlv_find_tags(tlv_data, [tag], num_results)
-def pack(tlv_data, recalculate_length = False):
+def pack(tlv_data, recalculate_length=False):
result = []
-
+
for data in tlv_data:
tag, length, value = data[:3]
if tag in (0xff, 0x00):
- result.append( chr(tag) )
+ result.append(chr(tag))
continue
-
+
if not isinstance(value, str):
value = pack(value, recalculate_length)
-
+
if recalculate_length:
length = len(value)
-
+
t = ""
while tag > 0:
- t = chr( tag & 0xff ) + t
+ t = chr(tag & 0xff) + t
tag = tag >> 8
-
+
if length < 0x7F:
l = chr(length)
else:
l = ""
while length > 0:
- l = chr( length & 0xff ) + l
+ l = chr(length & 0xff) + l
length = length >> 8
assert len(l) < 0x7f
- l = chr( 0x80 | len(l) ) + l
-
+ l = chr(0x80 | len(l)) + l
+
result.append(t)
result.append(l)
result.append(value)
-
+
return "".join(result)
-def bertlv_pack(data):
+def bertlv_pack(data):
"""Packs a bertlv list of 3-tuples (tag, length, newvalue) into a string"""
return pack(data)
-def unpack(data, with_marks = None, offset = 0, include_filler=False):
+def unpack(data, with_marks=None, offset=0, include_filler=False):
result = []
while len(data) > 0:
if ord(data[0]) in (0x00, 0xFF):
if include_filler:
if with_marks is None:
- result.append( (ord(data[0]), None, None) )
+ result.append((ord(data[0]), None, None))
else:
- result.append( (ord(data[0]), None, None, () ) )
+ result.append((ord(data[0]), None, None, ()))
data = data[1:]
offset = offset + 1
continue
-
+
l = len(data)
ber_class, constructed, tag, length, value, data = tlv_unpack(data)
stop = offset + (l - len(data))
start = stop - length
-
+
if with_marks is not None:
marks = []
for type, mark_start, mark_stop in with_marks:
@@ -176,24 +181,25 @@ def unpack(data, with_marks = None, offset = 0, include_filler=False):
marks = (marks, )
else:
marks = ()
-
+
if not constructed:
- result.append( (tag, length, value) + marks )
+ result.append((tag, length, value) + marks)
else:
- result.append( (tag, length, unpack(value, with_marks, offset = start)) + marks )
-
+ result.append((tag, length,
+ unpack(value, with_marks, offset=start)) + marks)
+
offset = stop
-
+
return result
-def bertlv_unpack(data):
+def bertlv_unpack(data):
"""Unpacks a bertlv coded string into a list of 3-tuples (tag, length,
newvalue)."""
return unpack(data)
-def simpletlv_pack(tlv_data, recalculate_length = False):
+def simpletlv_pack(tlv_data, recalculate_length=False):
result = ""
for tag, length, value in tlv_data:
@@ -210,12 +216,13 @@ def simpletlv_pack(tlv_data, recalculate_length = False):
if length < 0xff:
result += chr(tag) + chr(length) + value
else:
- result += chr(tag) + chr(0xff)+chr(length>>8)+chr(length&0xff) + value
+ result += chr(tag) + chr(0xff) + chr(length >> 8) + \
+ chr(length & 0xff) + value
return result
-def simpletlv_unpack(data):
+def simpletlv_unpack(data):
"""Unpacks a simpletlv coded string into a list of 3-tuples (tag, length,
newvalue)."""
result = []
@@ -227,38 +234,39 @@ def simpletlv_unpack(data):
length = ord(rest[1])
if length == 0xff:
- length = (ord(rest[2])<<8) + ord(rest[3])
+ length = (ord(rest[2]) << 8) + ord(rest[3])
newvalue = rest[4:4+length]
- rest = rest[4+length:]
+ rest = rest[4+length:]
else:
newvalue = rest[2:2+length]
- rest = rest[2+length:]
+ rest = rest[2+length:]
result.append((tag, length, newvalue))
return result
-
-def decodeDiscretionaryDataObjects(tlv_data):
+def decodeDiscretionaryDataObjects(tlv_data):
datalist = []
- for (tag, length, newvalue) in (tlv_find_tags(tlv_data,
- [TAG["DISCRETIONARY_DATA"], TAG["DISCRETIONARY_TEMPLATE"]])):
+ tlv_tags = (tlv_find_tags(tlv_data, [TAG["DISCRETIONARY_DATA"],
+ TAG["DISCRETIONARY_TEMPLATE"]]))
+ for (tag, length, newvalue) in tlv_tags:
datalist.append(newvalue)
return datalist
-def decodeOffsetDataObjects(tlv_data):
+
+def decodeOffsetDataObjects(tlv_data):
offsets = []
for (tag, length, newvalue) in tlv_find_tag(tlv_data,
- TAG["OFFSET_DATA"]):
+ TAG["OFFSET_DATA"]):
offsets.append(stringtoint(newvalue))
return offsets
-def decodeTagList(tlv_data):
+def decodeTagList(tlv_data):
taglist = []
for (t, l, data) in tlv_find_tag(tlv_data, TAG["TAG_LIST"]):
while data != "":
- tag = ord(data[0])
+ tag = ord(data[0])
data = data[1:]
if (tag & 0x1F) == 0x1F:
tag = (tag << 8) | ord(data[0])
@@ -270,11 +278,11 @@ def decodeTagList(tlv_data):
return taglist
-def decodeHeaderList(tlv_data):
+def decodeHeaderList(tlv_data):
headerlist = []
for (t, l, data) in tlv_find_tag(tlv_data, TAG["HEADER_LIST"]):
while data != "":
- tag = ord(data[0])
+ tag = ord(data[0])
data = data[1:]
if (tag & 0x1F) == 0x1F:
tag = (tag << 8) | ord(data[0])
@@ -289,7 +297,7 @@ def decodeHeaderList(tlv_data):
elif length & 0x80 == 0x80:
length_ = 0
data = data[1:]
- for i in range(0,length & 0x7F):
+ for i in range(0, length & 0x7F):
length_ = length_ * 256 + ord(data[0])
data = data[1:]
length = length_
@@ -298,20 +306,21 @@ def decodeHeaderList(tlv_data):
return headerlist
-def decodeExtendedHeaderList(tlv_data):
+def decodeExtendedHeaderList(tlv_data):
# TODO
return []
-def encodebertlvDatalist(tag, datalist):
+def encodebertlvDatalist(tag, datalist):
tlvlist = []
for data in datalist:
tlvlist.append((tag, len(data), data))
return bertlv_pack(tlvlist)
-def encodeDiscretionaryDataObjects(datalist):
+
+def encodeDiscretionaryDataObjects(datalist):
return encodebertlvDatalist(TAG["DISCRETIONARY_DATA"], datalist)
-def encodeDataOffsetObjects(datalist):
- return encodebertlvDatalist(TAG["OFFSET_DATA"], datalist)
+def encodeDataOffsetObjects(datalist):
+ return encodebertlvDatalist(TAG["OFFSET_DATA"], datalist)
diff --git a/virtualsmartcard/src/vpicc/virtualsmartcard/VirtualSmartcard.py b/virtualsmartcard/src/vpicc/virtualsmartcard/VirtualSmartcard.py
index 3c1875f..22399a9 100644
--- a/virtualsmartcard/src/vpicc/virtualsmartcard/VirtualSmartcard.py
+++ b/virtualsmartcard/src/vpicc/virtualsmartcard/VirtualSmartcard.py
@@ -1,38 +1,42 @@
#
# Copyright (C) 2011 Frank Morgner, Dominik Oepen
-#
+#
# This file is part of virtualsmartcard.
-#
+#
# 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
# Software Foundation, either version 3 of the License, or (at your option) any
# later version.
-#
+#
# virtualsmartcard is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
# more details.
-#
+#
# You should have received a copy of the GNU General Public License along with
# virtualsmartcard. If not, see .
#
+import atexit
+import logging
+import signal
+import socket
+import struct
+import sys
from virtualsmartcard.ConstantDefinitions import MAX_EXTENDED_LE, MAX_SHORT_LE
from virtualsmartcard.SWutils import SwError, SW
from virtualsmartcard.SmartcardFilesystem import make_property
from virtualsmartcard.utils import C_APDU, R_APDU, hexdump, inttostring
from virtualsmartcard.CardGenerator import CardGenerator
-import socket, struct, sys, signal, atexit, logging
-
-class SmartcardOS(object):
+class SmartcardOS(object):
"""Base class for a smart card OS"""
def getATR(self):
"""Returns the ATR of the card as string of characters"""
return ""
-
+
def powerUp(self):
"""Powers up the card"""
pass
@@ -47,24 +51,21 @@ class SmartcardOS(object):
def execute(self, msg):
"""Returns response to the given APDU as string of characters
-
+
:param msg: the APDU as string of characters
"""
return ""
-class Iso7816OS(SmartcardOS):
+class Iso7816OS(SmartcardOS):
- mf = make_property("mf", "master file")
+ mf = make_property("mf", "master file")
SAM = make_property("SAM", "secure access module")
def __init__(self, mf, sam, ins2handler=None, extended_length=False):
self.mf = mf
self.SAM = sam
- #if self.mf == None and self.SAM == None:
- # self.mf, self.SAM = generate_iso_card()
-
if not ins2handler:
self.ins2handler = {
0x0c: self.mf.eraseRecord,
@@ -111,33 +112,35 @@ class Iso7816OS(SmartcardOS):
self.lastCommandOffcut = ""
self.lastCommandSW = SW["NORMAL"]
- card_capabilities = self.mf.firstSFT + self.mf.secondSFT + \
- Iso7816OS.makeThirdSoftwareFunctionTable(extendedLe = extended_length)
- self.atr = Iso7816OS.makeATR(T=1, directConvention = True, TA1=0x13,
- histChars = chr(0x80) + chr(0x70 + len(card_capabilities)) +
- card_capabilities)
+ el = extended_length # only needed to keep following line short
+ tsft = Iso7816OS.makeThirdSoftwareFunctionTable(extendedLe=el)
+ card_capabilities = self.mf.firstSFT + self.mf.secondSFT + tsft
+ self.atr = Iso7816OS.makeATR(T=1, directConvention=True, TA1=0x13,
+ histChars=chr(0x80) +
+ chr(0x70 + len(card_capabilities)) +
+ card_capabilities)
def getATR(self):
return self.atr
-
+
@staticmethod
- def makeATR(**args):
+ def makeATR(**args):
"""Calculate Answer to Reset (ATR) and returns the bitstring.
-
- - directConvention (bool): Whether to use direct convention or
+
+ - directConvention (bool): Whether to use direct convention or
inverse convention.
- TAi, TBi, TCi (optional): Value between 0 and 0xff. Interface
Characters (for meaning see ISO 7816-3). Note that
- if no transmission protocol is given, it is
- automatically selected with T=max{j-1|TAj in args
+ if no transmission protocol is given, it is
+ automatically selected with T=max{j-1|TAj in args
OR TBj in args OR TCj in args}.
- - T (optional): Value between 0 and 15. Transmission Protocol.
- Note that if T is set, TAi/TBi/TCi for i>T are
+ - T (optional): Value between 0 and 15. Transmission Protocol.
+ Note that if T is set, TAi/TBi/TCi for i>T are
omitted.
- histChars (optional): Bitstring with 0 <= len(histChars) <= 15.
- Historical Characters T1 to T15 (for meaning
- see ISO 7816-4).
-
+ Historical Characters T1 to T15 (for
+ meaning see ISO 7816-4).
+
T0, TDi and TCK are automatically calculated.
"""
# first byte TS
@@ -145,8 +148,8 @@ class Iso7816OS(SmartcardOS):
atr = "\x3b"
else:
atr = "\x3f"
-
- if args.has_key("T"):
+
+ if "T" in args:
T = args["T"]
else:
T = 0
@@ -155,59 +158,62 @@ class Iso7816OS(SmartcardOS):
maxTD = 0
i = 15
while i > 0:
- if args.has_key("TA" + str(i)) or args.has_key("TB" + str(i)) or args.has_key("TC" + str(i)):
+ if ("TA" + str(i) in args or "TB" + str(i) in args or
+ "TC" + str(i) in args):
maxTD = i-1
break
i -= 1
if maxTD == 0 and T > 0:
maxTD = 2
-
+
# insert TDi into args (TD0 is actually T0)
for i in range(0, maxTD+1):
- if i == 0 and args.has_key("histChars"):
+ if i == 0 and "histChars" in args:
args["TD0"] = len(args["histChars"])
else:
args["TD"+str(i)] = T
if i < maxTD:
- args["TD"+str(i)] |= 1<<7
+ args["TD"+str(i)] |= 1 << 7
+
+ if "TA" + str(i+1) in args:
+ args["TD"+str(i)] |= 1 << 4
+ if "TB" + str(i+1) in args:
+ args["TD"+str(i)] |= 1 << 5
+ if "TC" + str(i+1) in args:
+ args["TD"+str(i)] |= 1 << 6
- if args.has_key("TA" + str(i+1)):
- args["TD"+str(i)] |= 1<<4
- if args.has_key("TB" + str(i+1)):
- args["TD"+str(i)] |= 1<<5
- if args.has_key("TC" + str(i+1)):
- args["TD"+str(i)] |= 1<<6
-
# initialize checksum
TCK = 0
-
+
# add TDi, TAi, TBi and TCi to ATR (TD0 is actually T0)
for i in range(0, maxTD+1):
atr = atr + "%c" % args["TD" + str(i)]
TCK ^= args["TD" + str(i)]
for j in ["A", "B", "C"]:
- if args.has_key("T" + j + str(i+1)):
+ if "T" + j + str(i+1) in args:
atr += "%c" % args["T" + j + str(i+1)]
# calculate checksum for all bytes from T0 to the end
TCK ^= args["T" + j + str(i+1)]
-
+
# add historical characters
- if args.has_key("histChars"):
+ if "histChars" in args:
atr += args["histChars"]
for i in range(0, len(args["histChars"])):
- TCK ^= ord( args["histChars"][i] )
-
+ TCK ^= ord(args["histChars"][i])
+
# checksum is omitted for T=0
if T > 0:
atr += "%c" % TCK
-
+
return atr
-
+
@staticmethod
def makeThirdSoftwareFunctionTable(commandChainging=False,
- extendedLe=False, assignLogicalChannel=0, maximumChannels=0):
+ extendedLe=False,
+ assignLogicalChannel=0,
+ maximumChannels=0):
"""
Returns a byte according to the third software function table from the
historical bytes of the card capabilities.
@@ -218,15 +224,14 @@ class Iso7816OS(SmartcardOS):
if extendedLe:
tsft |= 1 << 6
if assignLogicalChannel:
- if not (0<=assignLogicalChannel and assignLogicalChannel<=3):
+ if not (0 <= assignLogicalChannel and assignLogicalChannel <= 3):
raise ValueError
tsft |= assignLogicalChannel << 3
if maximumChannels:
- if not (0<=maximumChannels and maximumChannels<=7):
+ if not (0 <= maximumChannels and maximumChannels <= 7):
raise ValueError
tsft |= maximumChannels
return inttostring(tsft)
-
def formatResult(self, seekable, le, data, sw, sm):
if not seekable:
@@ -241,7 +246,7 @@ class Iso7816OS(SmartcardOS):
if le > len(data):
sw = SW["WARN_EOFBEFORENEREAD"]
- if le != None:
+ if le is not None:
result = data[:le]
else:
result = data[:0]
@@ -252,7 +257,8 @@ class Iso7816OS(SmartcardOS):
@staticmethod
def seekable(ins):
- if ins in [0xb0, 0xb1, 0xd0, 0xd1, 0xd6, 0xd7, 0xa0, 0xa1, 0xb2, 0xb3, 0xdc, 0xdd ]:
+ if ins in [0xb0, 0xb1, 0xd0, 0xd1, 0xd6, 0xd7, 0xa0, 0xa1, 0xb2, 0xb3,
+ 0xdc, 0xdd]:
return True
else:
return False
@@ -266,8 +272,8 @@ class Iso7816OS(SmartcardOS):
def execute(self, msg):
def notImplemented(*argz, **args):
"""
- If an application tries to use a function which is not implemented
- by the currently emulated smartcard we raise an exception which
+ If an application tries to use a function which is not implemented
+ by the currently emulated smartcard we raise an exception which
should result in an appropriate response APDU being passed to the
application.
"""
@@ -277,67 +283,71 @@ class Iso7816OS(SmartcardOS):
c = C_APDU(msg)
except ValueError as e:
logging.warning(str(e))
- return self.formatResult(False, 0, "", SW["ERR_INCORRECTPARAMETERS"], False)
+ return self.formatResult(False, 0, "",
+ SW["ERR_INCORRECTPARAMETERS"], False)
logging.info("Parsed APDU:\n%s", str(c))
-
- #Handle Class Byte
- #{{{
+
+ # Handle Class Byte
+ # {{{
class_byte = c.cla
SM_STATUS = None
logical_channel = 0
command_chaining = 0
header_authentication = 0
-
- #Ugly Hack for OpenSC-explorer
+
+ # Ugly Hack for OpenSC-explorer
if(class_byte == 0xb0):
logging.debug("Open SC APDU")
SM_STATUS = "No SM"
-
- #If Bit 8,7,6 == 0 then first industry values are used
+
+ # If Bit 8,7,6 == 0 then first industry values are used
if (class_byte & 0xE0 == 0x00):
- #Bit 1 and 2 specify the logical channel
+ # Bit 1 and 2 specify the logical channel
logical_channel = class_byte & 0x03
- #Bit 3 and 4 specify secure messaging
+ # Bit 3 and 4 specify secure messaging
secure_messaging = class_byte >> 2
secure_messaging &= 0x03
if (secure_messaging == 0x00):
SM_STATUS = "No SM"
elif (secure_messaging == 0x01):
- SM_STATUS = "Proprietary SM" # Not supported ?
+ SM_STATUS = "Proprietary SM" # Not supported ?
elif (secure_messaging == 0x02):
- SM_STATUS = "Standard SM"
+ SM_STATUS = "Standard SM"
elif (secure_messaging == 0x03):
SM_STATUS = "Standard SM"
header_authentication = 1
- #If Bit 8,7 == 01 then further industry values are used
+ # If Bit 8,7 == 01 then further industry values are used
elif (class_byte & 0x0C == 0x0C):
- #Bit 1 to 4 specify logical channel. 4 is added, value range is from
- #four to nineteen
+ # Bit 1 to 4 specify logical channel. 4 is added, value range is
+ # from four to nineteen
logical_channel = class_byte & 0x0f
logical_channel += 4
- #Bit 6 indicates secure messaging
+ # Bit 6 indicates secure messaging
secure_messaging = class_byte >> 6
if (secure_messaging == 0x00):
- SM_STATUS = "No SM"
+ SM_STATUS = "No SM"
elif (secure_messaging == 0x01):
SM_STATUS = "Standard SM"
else:
# Bit 8 is set to 1, which is not specified by ISO 7816-4
SM_STATUS = "Proprietary SM"
- #In both cases Bit 5 specifies command chaining
+ # In both cases Bit 5 specifies command chaining
command_chaining = class_byte >> 5
command_chaining &= 0x01
- #}}}
-
+ # }}}
+
sm = False
- try:
+ try:
if SM_STATUS == "Standard SM" or SM_STATUS == "Proprietary SM":
c = self.SAM.parse_SM_CAPDU(c, header_authentication)
logging.info("Decrypted APDU:\n%s", str(c))
sm = True
- sw, result = self.ins2handler.get(c.ins, notImplemented)(c.p1, c.p2, c.data)
- answer = self.formatResult(Iso7816OS.seekable(c.ins), c.effective_Le, result, sw, sm)
+ sw, result = self.ins2handler.get(c.ins, notImplemented)(c.p1,
+ c.p2,
+ c.data)
+ answer = self.formatResult(Iso7816OS.seekable(c.ins),
+ c.effective_Le, result, sw, sm)
except SwError as e:
logging.info(e.message)
import traceback
@@ -355,20 +365,19 @@ class Iso7816OS(SmartcardOS):
self.mf.current = self.mf
-
# sizeof(int) taken from asizof-package {{{
_Csizeof_short = len(struct.pack('h', 0))
# }}}
-VPCD_CTRL_LEN = 1
-
-VPCD_CTRL_OFF = 0
-VPCD_CTRL_ON = 1
+VPCD_CTRL_LEN = 1
+VPCD_CTRL_OFF = 0
+VPCD_CTRL_ON = 1
VPCD_CTRL_RESET = 2
-VPCD_CTRL_ATR = 4
+VPCD_CTRL_ATR = 4
-class VirtualICC(object):
+
+class VirtualICC(object):
"""
This class is responsible for maintaining the communication of the virtual
PCD and the emulated smartcard. vpicc and vpcd communicate via a socket.
@@ -376,42 +385,50 @@ class VirtualICC(object):
vicc. The vicc passes these CAPDUs on to an emulated smartcard, which
produces a response APDU. This RAPDU is then passed back by the vicc to
the vpcd, which forwards it to the application.
- """
-
- def __init__(self, filename, datasetfile, card_type, host, port, readernum=None, ef_cardsecurity=None, ef_cardaccess=None, ca_key=None, cvca=None, disable_checks=False, esign_key=None, esign_ca_cert=None, esign_cert=None, logginglevel=logging.INFO):
+ """
+
+ def __init__(self, filename, datasetfile, card_type, host, port,
+ readernum=None, ef_cardsecurity=None, ef_cardaccess=None,
+ ca_key=None, cvca=None, disable_checks=False, esign_key=None,
+ esign_ca_cert=None, esign_cert=None,
+ logginglevel=logging.INFO):
from os.path import exists
-
- logging.basicConfig(level = logginglevel,
- format = "%(asctime)s [%(levelname)s] %(message)s",
- datefmt = "%d.%m.%Y %H:%M:%S")
-
+
+ logging.basicConfig(level=logginglevel,
+ format="%(asctime)s [%(levelname)s] %(message)s",
+ datefmt="%d.%m.%Y %H:%M:%S")
+
self.filename = None
self.cardGenerator = CardGenerator(card_type)
-
- #If a filename is specified, try to load the card from disk
- if filename != None:
+
+ # If a filename is specified, try to load the card from disk
+ if filename is not None:
self.filename = filename
if exists(filename):
self.cardGenerator.loadCard(self.filename)
else:
logging.info("Creating new card which will be saved in %s.",
- self.filename)
+ self.filename)
- #If a dataset file is specified, read the card's data groups from disk
- if datasetfile != None:
+ # If a dataset file is specified, read the card's data groups from disk
+ if datasetfile is not None:
if exists(datasetfile):
logging.info("Reading Data Groups from file %s.",
- datasetfile)
+ datasetfile)
self.cardGenerator.readDatagroups(datasetfile)
MF, SAM = self.cardGenerator.getCard()
-
- #Generate an OS object of the correct card_type
+
+ # Generate an OS object of the correct card_type
if card_type == "iso7816" or card_type == "ePass":
self.os = Iso7816OS(MF, SAM)
elif card_type == "nPA":
from virtualsmartcard.cards.nPA import NPAOS
- self.os = NPAOS(MF, SAM, ef_cardsecurity=ef_cardsecurity, ef_cardaccess=ef_cardaccess, ca_key=ca_key, cvca=cvca, disable_checks=disable_checks, esign_key=esign_key, esign_ca_cert=esign_ca_cert, esign_cert=esign_cert)
+ self.os = NPAOS(MF, SAM, ef_cardsecurity=ef_cardsecurity,
+ ef_cardaccess=ef_cardaccess, ca_key=ca_key,
+ cvca=cvca, disable_checks=disable_checks,
+ esign_key=esign_key, esign_ca_cert=esign_ca_cert,
+ esign_cert=esign_cert)
elif card_type == "cryptoflex":
from virtualsmartcard.cards.cryptoflex import CryptoflexOS
self.os = CryptoflexOS(MF, SAM)
@@ -422,13 +439,13 @@ class VirtualICC(object):
from virtualsmartcard.cards.HandlerTest import HandlerTestOS
self.os = HandlerTestOS()
else:
- logging.warning("Unknown cardtype %s. Will use standard card_type (ISO 7816)",
- card_type)
+ logging.warning("Unknown cardtype %s. Will use standard card_type \
+ (ISO 7816)", card_type)
card_type = "iso7816"
self.os = Iso7816OS(MF, SAM)
self.type = card_type
-
- #Connect to the VPCD
+
+ # Connect to the VPCD
self.host = host
self.port = port
if host:
@@ -439,8 +456,8 @@ class VirtualICC(object):
self.server_sock = None
except socket.error as e:
logging.error("Failed to open socket: %s", str(e))
- logging.error("Is pcscd running at %s? Is vpcd loaded? Is a firewall blocking port %u?",
- host, port)
+ logging.error("Is pcscd running at %s? Is vpcd loaded? Is a \
+ firewall blocking port %u?", host, port)
sys.exit()
else:
# use reversed connection mode
@@ -449,15 +466,16 @@ class VirtualICC(object):
self.sock.settimeout(None)
except socket.error as e:
logging.error("Failed to open socket: %s", str(e))
- logging.error("Is pcscd running? Is vpcd loaded and in reversed connection mode? Is a firewall blocking port %u?",
- port)
+ logging.error("Is pcscd running? Is vpcd loaded and in \
+ reversed connection mode? Is a firewall \
+ blocking port %u?", port)
sys.exit()
logging.info("Connected to virtual PCD at %s:%u", host, port)
signal.signal(signal.SIGINT, self.signalHandler)
atexit.register(self.stop)
-
+
def signalHandler(self, sig=None, frame=None):
"""Basically this signal handler just surpresses a traceback from being
printed when the user presses crtl-c"""
@@ -512,7 +530,7 @@ class VirtualICC(object):
vpcd, dispatches them to the emulated smartcard and sends the resulting
respsonse APDU back to the vpcd.
"""
- while True :
+ while True:
try:
(size, msg) = self.__recvFromVPICC()
except socket.error as e:
@@ -524,7 +542,8 @@ class VirtualICC(object):
sys.exit()
if not size:
- logging.warning("Error in communication protocol (missing size parameter)")
+ logging.warning("Error in communication protocol (missing \
+ size parameter)")
elif size == VPCD_CTRL_LEN:
if msg == chr(VPCD_CTRL_OFF):
logging.info("Power Down")
@@ -553,7 +572,6 @@ class VirtualICC(object):
self.sock.close()
if self.server_sock:
self.server_sock.close()
- if self.filename != None:
+ if self.filename is not None:
self.cardGenerator.setCard(self.os.mf, self.os.SAM)
self.cardGenerator.saveCard(self.filename)
-
diff --git a/virtualsmartcard/src/vpicc/virtualsmartcard/tests/CardGenerator_test.py b/virtualsmartcard/src/vpicc/virtualsmartcard/tests/CardGenerator_test.py
index 1fbc8fd..e8eb97d 100644
--- a/virtualsmartcard/src/vpicc/virtualsmartcard/tests/CardGenerator_test.py
+++ b/virtualsmartcard/src/vpicc/virtualsmartcard/tests/CardGenerator_test.py
@@ -24,6 +24,7 @@ import unittest
from virtualsmartcard.CardGenerator import CardGenerator
+
class ISO7816GeneratorTest(unittest.TestCase):
card_type = 'iso7816'
@@ -44,7 +45,7 @@ class ISO7816GeneratorTest(unittest.TestCase):
def test_load_card_from_file(self):
self.card_generator.generateCard()
self.card_generator.saveCard(self.filename)
- local_generator= CardGenerator(self.card_type)
+ local_generator = CardGenerator(self.card_type)
local_generator.password = self.card_generator.password
local_generator.loadCard(self.filename)
mf, sam = local_generator.getCard()
@@ -59,21 +60,30 @@ class ISO7816GeneratorTest(unittest.TestCase):
def test_get_and_set_card(self):
self.card_generator.generateCard()
mf, sam = self.card_generator.getCard()
- local_generator= CardGenerator(self.card_type)
+ local_generator = CardGenerator(self.card_type)
local_generator.setCard(mf, sam)
+
class TestNPACardGenerator(ISO7816GeneratorTest):
card_type = 'nPA'
+ def setUp(self):
+ self.filename = tempfile.mktemp()
+ self.card_generator = CardGenerator(self.card_type)
+ self.card_generator.password = "TestPassword"
+ self.test_readDatagroups_file = "/../../../../npa-example-data/"\
+ "Example_Dataset_Mueller_Gertrud.txt"
+
def test_readDatagroups(self):
path = os.path.dirname(__file__)
- datagroupsFile = path + "/../../../../npa-example-data/Example_Dataset_Mueller_Gertrud.txt"
+ datagroupsFile = path + self.test_readDatagroups_file
self.card_generator.readDatagroups(datagroupsFile)
mf, sam = self.card_generator.getCard()
self.assertIsNotNone(mf)
self.assertIsNotNone(sam)
+
class CryptoflexGeneratorTest(ISO7816GeneratorTest):
card_type = 'cryptoflex'
@@ -81,7 +91,7 @@ class CryptoflexGeneratorTest(ISO7816GeneratorTest):
# Not tested because an ePass card currently cannot be generated without user
# interaction.
#
-#class ePassGeneratorTest(ISO7816GeneratorTest):
+# class ePassGeneratorTest(ISO7816GeneratorTest):
#
# card_type = 'ePass'
diff --git a/virtualsmartcard/src/vpicc/virtualsmartcard/tests/CryptoUtils_test.py b/virtualsmartcard/src/vpicc/virtualsmartcard/tests/CryptoUtils_test.py
index 9ba7c32..66d3509 100644
--- a/virtualsmartcard/src/vpicc/virtualsmartcard/tests/CryptoUtils_test.py
+++ b/virtualsmartcard/src/vpicc/virtualsmartcard/tests/CryptoUtils_test.py
@@ -20,20 +20,28 @@
import unittest
from virtualsmartcard.CryptoUtils import *
+
class TestCryptoUtils(unittest.TestCase):
def setUp(self):
- self.teststring = "DEADBEEFistatsyksdvhwohfwoehcowc8hw8rogfq8whv75tsgohsav8wress"
+ self.teststring = "DEADBEEFistatsyksdvhwohfwoehcowc8hw8rogfq8whv75tsg"\
+ "ohsav8wress"
self.testpass = "SomeRandomPassphrase"
- # The following string was generated using the proteced string method and
- # is used as regression test.
+ # The following string was generated using the proteced string method
+ # and is used as a regression test.
# The data generated by protect_string should actually consist of
# printable characters only but that would break backwards
# compatibility with the (buggy) legacy implementation
- self.protectedTestString = "2470356b32242478424f50746f6d712448b8f6285ac8462fffc6aef921f2ad84855219c5aaafb39c4cc9e54d1634c60cfc9347c67fa55967c5b0130469c96a44f0b73c53f5ddfc43cd8c1ef68965ebb23330393265383732333937353465653838643135363830666637336134316532".decode('hex')
+ self.protectedTestString = "2470356b32242478424f50746f6d712448b8f6285"\
+ "ac8462fffc6aef921f2ad84855219c5aaafb39c4c"\
+ "c9e54d1634c60cfc9347c67fa55967c5b0130469c"\
+ "96a44f0b73c53f5ddfc43cd8c1ef68965ebb23330"\
+ "39326538373233393735346565383864313536383"\
+ "0666637336134316532".decode('hex')
self.salt = "POcwYIHr"
self.cryptedWord = "$p5k2$$POcwYIHr$SPaWqD3NpmLZc6gXbeybnAoCxo7Oc//K"
- self.cryptedWordThousandIterations = "$p5k2$3e8$POcwYIHr$f/mEOCulo6v7Nq2ooS3480xTet6zdGbI"
+ self.cryptedWordThousandIterations = "$p5k2$3e8$POcwYIHr$f/mEOCulo6v7"\
+ "Nq2ooS3480xTet6zdGbI"
def test_padding(self):
padded = append_padding(16, self.teststring)
@@ -42,11 +50,13 @@ class TestCryptoUtils(unittest.TestCase):
def test_protect_string(self):
protectedString = protect_string(self.teststring, self.testpass)
- unprotectedString = read_protected_string(protectedString, self.testpass)
+ unprotectedString = read_protected_string(protectedString,
+ self.testpass)
self.assertEqual(self.teststring, unprotectedString)
def test_unprotect_string(self):
- unprotectedString = read_protected_string(self.protectedTestString, self.testpass)
+ unprotectedString = read_protected_string(self.protectedTestString,
+ self.testpass)
self.assertEqual(unprotectedString, self.teststring)
def test_crypt(self):
diff --git a/virtualsmartcard/src/vpicc/virtualsmartcard/tests/SmartcardSAM_test.py b/virtualsmartcard/src/vpicc/virtualsmartcard/tests/SmartcardSAM_test.py
index a07f426..239de3a 100644
--- a/virtualsmartcard/src/vpicc/virtualsmartcard/tests/SmartcardSAM_test.py
+++ b/virtualsmartcard/src/vpicc/virtualsmartcard/tests/SmartcardSAM_test.py
@@ -20,13 +20,13 @@
import unittest
from virtualsmartcard.SmartcardSAM import *
-#Unit Tests
+
class TestSmartcardSAM(unittest.TestCase):
def setUp(self):
self.password = "DUMMYKEYDUMMYKEY"
self.myCard = SAM("1234", "1234567890")
- self.secEnv = Security_Environment(None, self.myCard) #TODO: Set CRTs
+ self.secEnv = Security_Environment(None, self.myCard) # TODO: Set CRTs
self.secEnv.ht.algorithm = "SHA"
self.secEnv.ct.algorithm = "AES-CBC"
@@ -54,20 +54,24 @@ class TestSmartcardSAM(unittest.TestCase):
blocklen = vsCrypto.get_cipher_blocklen("DES3-ECB")
padded = vsCrypto.append_padding(blocklen, challenge)
sw, result_data = self.myCard.internal_authenticate(0x00, 0x00, padded)
- sw, result_data = self.myCard.external_authenticate(0x00, 0x00, result_data)
+ sw, result_data = self.myCard.external_authenticate(0x00, 0x00,
+ result_data)
self.assertEquals(sw, SW["NORMAL"])
def test_security_environment(self):
hash = self.secEnv.hash(0x90, 0x80, self.password)
- #The API should be changed so that the hash function returns SW_NORMAL
+ # The API should be changed so that the hash function returns SW_NORMAL
self.secEnv.ct.key = hash[:16]
- crypted = self.secEnv.encipher(0x00, 0x00, self.password)
- #The API should be changed so that the encipher function returns SW_NORMAL
+ crypted = self.secEnv.encipher(0x00, 0x00,
+ self.password)
+ # The API should be changed so that encipher() returns SW_NORMAL
plain = self.secEnv.decipher(0x00, 0x00, crypted)
- #The API should be changed so that the decipher function returns SW_NORMAL
- #self.assertEqual(plain, self.password)
- #secEnv.decipher doesn't strip padding. Should it?
- self.secEnv.ct.algorithm = "RSA" #should this really be secEnv.ct? probably rather secEnv.dst
+ # The API should be changed so that decipher() returns SW_NORMAL
+ # self.assertEqual(plain, self.password)
+ # secEnv.decipher doesn't strip padding. Should it?
+
+ # should this really be secEnv.ct? probably rather secEnv.dst
+ self.secEnv.ct.algorithm = "RSA"
self.secEnv.dst.keylength = 1024
sw, pk = self.secEnv.generate_public_key_pair(0x00, 0x00, "")
self.assertEquals(sw, SW["NORMAL"])
@@ -76,6 +80,6 @@ class TestSmartcardSAM(unittest.TestCase):
if __name__ == "__main__":
unittest.main()
- #CF = CryptoflexSE(None)
- #print CF.generate_public_key_pair(0x00, 0x80, "\x01\x00\x01\x00")
- #print MyCard._get_referenced_key(0x01)
+ # CF = CryptoflexSE(None)
+ # print CF.generate_public_key_pair(0x00, 0x80, "\x01\x00\x01\x00")
+ # print MyCard._get_referenced_key(0x01)
diff --git a/virtualsmartcard/src/vpicc/virtualsmartcard/tests/utils_test.py b/virtualsmartcard/src/vpicc/virtualsmartcard/tests/utils_test.py
index dfe8e9c..3adf3e3 100644
--- a/virtualsmartcard/src/vpicc/virtualsmartcard/tests/utils_test.py
+++ b/virtualsmartcard/src/vpicc/virtualsmartcard/tests/utils_test.py
@@ -21,13 +21,14 @@
import unittest
from virtualsmartcard.utils import C_APDU, R_APDU, hexdump
+
class TestUtils(unittest.TestCase):
def test_CAPDU(self):
- a = C_APDU(1, 2, 3, 4) # case 1
- b = C_APDU(1, 2, 3, 4, 5) # case 2
- c = C_APDU((1, 2, 3), cla = 0x23, data = "hallo") # case 3
- d = C_APDU(1, 2, 3, 4, 2, 4, 6, 0) # case 4
+ a = C_APDU(1, 2, 3, 4) # case 1
+ b = C_APDU(1, 2, 3, 4, 5) # case 2
+ c = C_APDU((1, 2, 3), cla=0x23, data="hallo") # case 3
+ d = C_APDU(1, 2, 3, 4, 2, 4, 6, 0) # case 4
print()
print(a)
@@ -44,15 +45,41 @@ class TestUtils(unittest.TestCase):
for i in a, b, c, d:
print(hexdump(i.render()))
+ # case 2 extended length
print()
- g = C_APDU(0x00, 0xb0, 0x9c, 0x00, 0x00, 0x00, 0x00) #case 2 extended length
+ g = C_APDU(0x00, 0xb0, 0x9c, 0x00, 0x00, 0x00, 0x00)
print()
print(g)
print(repr(g))
print(hexdump(g.render()))
- h = C_APDU('\x0c\x2a\x00\xbe\x00\x01\x5f\x87\x82\x01\x51\x01\xf0\xa2\x21\xa1\x36\x27\xb1\x30\x31\x3e\xd0\x97\x09\xb5\xde\x73\x5e\x29\x90\xce\xf1\x3d\x8a\xfd\xe7\x92\xe5\xa4\x70\xb9\x5d\x31\xe2\x34\xe7\xe2\x06\x13\x17\x7a\x3e\xca\x06\x39\x24\x2e\x75\x8c\x29\x6d\xd8\xa3\x1b\x1a\x68\x58\xd0\x1a\x98\xd4\xd8\x19\x50\xe9\x1b\x3c\xd1\xfd\x10\x53\x5b\xf2\x3b\xff\x4a\xf6\x05\xd0\x72\xad\xae\xaa\x93\x1a\x0a\x90\xc8\xa1\xb1\xf1\x0a\xba\x5b\xd2\x23\x38\xf8\x9a\x38\x9e\xa2\x04\x8b\xcb\x8b\x8b\xc0\x80\xd9\x2a\x04\x47\x26\x83\xda\xfe\x57\x68\x6b\x00\xb9\xa2\xea\x96\xf2\x07\x7f\xc5\x9c\xee\xbe\xf3\x81\xbf\x24\x19\x1e\x49\x1e\x9a\x85\x8f\x34\xcb\x1a\x23\xae\x6d\x7f\xa4\xb6\x7b\x60\x5d\x56\x79\x1c\xec\x18\xcc\x09\xdb\xb2\xbb\xf4\x31\xee\x08\x54\x26\xd5\xde\x99\xfa\x43\xa2\x49\x8e\x60\xc0\xaa\x4f\xfd\xf7\xe5\xc8\x89\x43\x5e\x11\xa2\x28\xc4\x92\x11\xda\xba\xe4\x91\xec\x04\xc9\x2c\xbd\x91\x6a\x5e\x7e\xb9\x85\xa2\xfa\x07\xc9\x47\x24\xa4\x3b\x63\xef\x75\x65\xef\xaf\xac\x22\x75\x99\x8b\x19\xde\x95\x76\xc9\xc8\xbc\x30\x23\x48\x07\x28\x19\x1e\x49\x9e\xcb\x99\xc3\x48\xdd\x1d\x0f\x44\x62\x64\x2a\x19\x7b\xeb\xee\xdf\xa1\xa6\xae\x87\x6d\x93\x36\x2d\x35\x8f\xd9\x61\x73\xef\x2d\x39\xb5\xc5\xe2\x75\x4b\x63\x0b\x41\x94\x8c\xbb\x55\xf6\x98\x5f\x9c\x07\xca\xe3\x15\xe4\xe6\x93\xd0\xa3\x9b\x22\xfa\x62\x18\xc5\x63\xfa\x2d\x57\xbb\x29\x2d\x57\x10\xd3\x0c\x05\x80\x15\x27\x4b\xc0\x84\x23\x62\x22\x6b\xae\x39\xa2\x8f\x55\xac\x8e\x08\x34\x46\xcc\x83\xf9\x9d\x2a\x75\x00\x00')
+ h = C_APDU('\x0c\x2a\x00\xbe\x00\x01\x5f\x87\x82\x01\x51\x01\xf0\xa2'
+ '\x21\xa1\x36\x27\xb1\x30\x31\x3e\xd0\x97\x09\xb5\xde\x73'
+ '\x5e\x29\x90\xce\xf1\x3d\x8a\xfd\xe7\x92\xe5\xa4\x70\xb9'
+ '\x5d\x31\xe2\x34\xe7\xe2\x06\x13\x17\x7a\x3e\xca\x06\x39'
+ '\x24\x2e\x75\x8c\x29\x6d\xd8\xa3\x1b\x1a\x68\x58\xd0\x1a'
+ '\x98\xd4\xd8\x19\x50\xe9\x1b\x3c\xd1\xfd\x10\x53\x5b\xf2'
+ '\x3b\xff\x4a\xf6\x05\xd0\x72\xad\xae\xaa\x93\x1a\x0a\x90'
+ '\xc8\xa1\xb1\xf1\x0a\xba\x5b\xd2\x23\x38\xf8\x9a\x38\x9e'
+ '\xa2\x04\x8b\xcb\x8b\x8b\xc0\x80\xd9\x2a\x04\x47\x26\x83'
+ '\xda\xfe\x57\x68\x6b\x00\xb9\xa2\xea\x96\xf2\x07\x7f\xc5'
+ '\x9c\xee\xbe\xf3\x81\xbf\x24\x19\x1e\x49\x1e\x9a\x85\x8f'
+ '\x34\xcb\x1a\x23\xae\x6d\x7f\xa4\xb6\x7b\x60\x5d\x56\x79'
+ '\x1c\xec\x18\xcc\x09\xdb\xb2\xbb\xf4\x31\xee\x08\x54\x26'
+ '\xd5\xde\x99\xfa\x43\xa2\x49\x8e\x60\xc0\xaa\x4f\xfd\xf7'
+ '\xe5\xc8\x89\x43\x5e\x11\xa2\x28\xc4\x92\x11\xda\xba\xe4'
+ '\x91\xec\x04\xc9\x2c\xbd\x91\x6a\x5e\x7e\xb9\x85\xa2\xfa'
+ '\x07\xc9\x47\x24\xa4\x3b\x63\xef\x75\x65\xef\xaf\xac\x22'
+ '\x75\x99\x8b\x19\xde\x95\x76\xc9\xc8\xbc\x30\x23\x48\x07'
+ '\x28\x19\x1e\x49\x9e\xcb\x99\xc3\x48\xdd\x1d\x0f\x44\x62'
+ '\x64\x2a\x19\x7b\xeb\xee\xdf\xa1\xa6\xae\x87\x6d\x93\x36'
+ '\x2d\x35\x8f\xd9\x61\x73\xef\x2d\x39\xb5\xc5\xe2\x75\x4b'
+ '\x63\x0b\x41\x94\x8c\xbb\x55\xf6\x98\x5f\x9c\x07\xca\xe3'
+ '\x15\xe4\xe6\x93\xd0\xa3\x9b\x22\xfa\x62\x18\xc5\x63\xfa'
+ '\x2d\x57\xbb\x29\x2d\x57\x10\xd3\x0c\x05\x80\x15\x27\x4b'
+ '\xc0\x84\x23\x62\x22\x6b\xae\x39\xa2\x8f\x55\xac\x8e\x08'
+ '\x34\x46\xcc\x83\xf9\x9d\x2a\x75\x00\x00')
print()
print(h)
print(repr(h))
@@ -71,4 +98,3 @@ class TestUtils(unittest.TestCase):
print()
for i in e, f:
print(hexdump(i.render()))
-
diff --git a/virtualsmartcard/src/vpicc/virtualsmartcard/utils.py b/virtualsmartcard/src/vpicc/virtualsmartcard/utils.py
index 73c7115..e4b6394 100644
--- a/virtualsmartcard/src/vpicc/virtualsmartcard/utils.py
+++ b/virtualsmartcard/src/vpicc/virtualsmartcard/utils.py
@@ -1,41 +1,33 @@
#
# Copyright (C) 2006 Henryk Ploetz
-#
+#
# This file is part of virtualsmartcard.
-#
+#
# 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
# Software Foundation, either version 3 of the License, or (at your option) any
# later version.
-#
+#
# virtualsmartcard is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
# more details.
-#
+#
# You should have received a copy of the GNU General Public License along with
# virtualsmartcard. If not, see .
#
-import string, binascii
+import binascii
+import string
from ConstantDefinitions import MAX_SHORT_LE, MAX_EXTENDED_LE
-def stringtoint(str):
- #i = len(str) - 1
- #int = 0
- #while i >= 0:
- #int = (int << i*8) + ord(str[i])
- #i = i - 1
- #return int
+
+def stringtoint(str):
if str:
return int(str.encode('hex'), 16)
return 0
-def inttostring(i, length=None):
- #str = ""
- #while i > 0:
- #str = chr(i & 0xff) + str
- #i >>= 8
+def inttostring(i, length=None):
str = "%x" % i
if len(str) % 2 == 0:
str = str.decode('hex')
@@ -53,43 +45,50 @@ def inttostring(i, length=None):
_myprintable = " " + string.letters + string.digits + string.punctuation
-def hexdump(data, indent = 0, short = False, linelen = 16, offset = 0):
- """Generates a nice hexdump of data and returns it. Consecutive lines will
- be indented with indent spaces. When short is true, will instead generate
+
+
+def hexdump(data, indent=0, short=False, linelen=16, offset=0):
+ """Generates a nice hexdump of data and returns it. Consecutive lines will
+ be indented with indent spaces. When short is true, will instead generate
hexdump without adresses and on one line.
-
- Examples:
+
+ Examples:
hexdump('\x00\x41') -> \
'0000: 00 41 .A '
hexdump('\x00\x41', short=True) -> '00 41 (.A)'"""
-
+
def hexable(data):
return " ".join([binascii.b2a_hex(a) for a in data])
-
+
def printable(data):
return "".join([e in _myprintable and e or "." for e in data])
-
+
if short:
return "%s (%s)" % (hexable(data), printable(data))
-
- FORMATSTRING = "%04x: %-"+ str(linelen*3) +"s %-"+ str(linelen) +"s"
+
+ FORMATSTRING = "%04x: %-" + str(linelen*3) + "s %-" + str(linelen) + "s"
result = ""
(head, tail) = (data[:linelen], data[linelen:])
pos = 0
while len(head) > 0:
if pos > 0:
result = result + "\n%s" % (' ' * indent)
- result = result + FORMATSTRING % (pos+offset, hexable(head), printable(head))
+ result = result + FORMATSTRING % (pos+offset, hexable(head),
+ printable(head))
pos = pos + len(head)
(head, tail) = (tail[:linelen], tail[linelen:])
return result
LIFE_CYCLES = {0x01: "Load file = loaded",
- 0x03: "Applet instance / security domain = Installed",
- 0x07: "Card manager = Initialized; Applet instance / security domain = Selectable",
- 0x0F: "Card manager = Secured; Applet instance / security domain = Personalized",
- 0x7F: "Card manager = Locked; Applet instance / security domain = Blocked",
- 0xFF: "Applet instance = Locked"}
+ 0x03: "Applet instance / security domain = Installed",
+ 0x07: "Card manager = Initialized; Applet instance / security "
+ "domain = Selectable",
+ 0x0F: "Card manager = Secured; Applet instance / security "
+ "domain = Personalized",
+ 0x7F: "Card manager = Locked; Applet instance / security "
+ "domain = Blocked",
+ 0xFF: "Applet instance = Locked"}
+
def parse_status(data):
"""Parses the Response APDU of a GetStatus command."""
@@ -99,33 +98,36 @@ def parse_status(data):
return "N/A"
else:
privs = []
- if privileges & (1<<7):
+ if privileges & (1 << 7):
privs.append("security domain")
- if privileges & (1<<6):
+ if privileges & (1 << 6):
privs.append("DAP DES verification")
- if privileges & (1<<5):
+ if privileges & (1 << 5):
privs.append("delegated management")
- if privileges & (1<<4):
+ if privileges & (1 << 4):
privs.append("card locking")
- if privileges & (1<<3):
+ if privileges & (1 << 3):
privs.append("card termination")
- if privileges & (1<<2):
+ if privileges & (1 << 2):
privs.append("default selected")
- if privileges & (1<<1):
+ if privileges & (1 << 1):
privs.append("global PIN modification")
- if privileges & (1<<0):
+ if privileges & (1 << 0):
privs.append("mandated DAP verification")
return ", ".join(privs)
-
+
lgth = ord(segment[0])
aid = segment[1:1+lgth]
lifecycle = ord(segment[1+lgth])
privileges = ord(segment[1+lgth+1])
-
+
print("aid length: %i (%x)" % (lgth, lgth))
- print("aid: %s" % hexdump(aid, indent = 18, short=True))
- print("life cycle state: %x (%s)" % (lifecycle, LIFE_CYCLES.get(lifecycle, "unknown or invalid state")))
- print("privileges: %x (%s)\n" % (privileges, parse_privileges(privileges)))
+ print("aid: %s" % hexdump(aid, indent=18, short=True))
+ print("life cycle state: %x (%s)" %
+ (lifecycle, LIFE_CYCLES.get(lifecycle,
+ "unknown or invalid state")))
+ print("privileges: %x (%s)\n" %
+ (privileges, parse_privileges(privileges)))
pos = 0
while pos < len(data):
@@ -134,39 +136,43 @@ def parse_status(data):
parse_segment(segment)
pos = pos + lgth
+
def _unformat_hexdump(dump):
hexdump = " ".join([line[7:54] for line in dump.splitlines()])
return binascii.a2b_hex("".join([e != " " and e or "" for e in hexdump]))
+
def _make_byte_property(prop):
"Make a byte property(). This is meta code."
return property(lambda self: getattr(self, "_"+prop, None),
- lambda self, value: self._setbyte(prop, value),
- lambda self: delattr(self, "_"+prop),
- "The %s attribute of the APDU" % prop)
+ lambda self, value: self._setbyte(prop, value),
+ lambda self: delattr(self, "_"+prop),
+ "The %s attribute of the APDU" % prop)
+
class APDU(object):
"Base class for an APDU"
-
+
def __init__(self, *args, **kwargs):
- """Creates a new APDU instance. Can be given positional parameters which
+ """Creates a new APDU instance. Can be given positional parameters which
must be sequences of either strings (or strings themselves) or integers
- specifying byte values that will be concatenated in order. Alternatively
- you may give exactly one positional argument that is an APDU instance.
+ specifying byte values that will be concatenated in order.
+ Alternatively you may give exactly one positional argument that is an
+ APDU instance.
After all the positional arguments have been concatenated they must
form a valid APDU!
-
+
The keyword arguments can then be used to override those values.
- Keywords recognized are:
+ Keywords recognized are:
- C_APDU: cla, ins, p1, p2, lc, le, data
- R_APDU: sw, sw1, sw2, data
"""
-
+
initbuff = list()
-
+
if len(args) == 1 and isinstance(args[0], self.__class__):
- self.parse( args[0].render() )
+ self.parse(args[0].render())
else:
for arg in args:
if type(arg) == str:
@@ -179,103 +185,115 @@ class APDU(object):
initbuff.append(elem)
else:
initbuff.append(arg)
-
+
for (index, value) in enumerate(initbuff):
t = type(value)
if t == str:
initbuff[index] = ord(value)
elif t != int:
- raise TypeError("APDU must consist of ints or one-byte strings, not %s (index %s)" % (t, index))
-
- self.parse( initbuff )
-
+ raise TypeError("APDU must consist of ints or one-byte "
+ "strings, not %s (index %s)" % (t, index))
+
+ self.parse(initbuff)
+
for (name, value) in kwargs.items():
if value is not None:
setattr(self, name, value)
-
+
def _getdata(self):
return self._data
- def _setdata(self, value):
+
+ def _setdata(self, value):
if isinstance(value, str):
self._data = "".join([e for e in value])
elif isinstance(value, list):
self._data = "".join([chr(int(e)) for e in value])
else:
- raise ValueError("'data' attribute can only be a str or a list of int, not %s" % type(value))
+ raise ValueError("'data' attribute can only be a str or a list of "
+ "int, not %s" % type(value))
self.Lc = len(value)
+
def _deldata(self):
- del self._data; self.data = ""
-
+ del self._data
+ self.data = ""
+
data = property(_getdata, _setdata, None,
- "The data contents of this APDU")
-
+ "The data contents of this APDU")
+
def _setbyte(self, name, value):
- #print "setbyte(%r, %r)" % (name, value)
if isinstance(value, int):
setattr(self, "_"+name, value)
elif isinstance(value, str):
setattr(self, "_"+name, ord(value))
else:
- raise ValueError("'%s' attribute can only be a byte, that is: int or str, not %s" % (name, type(value)))
+ raise ValueError("'%s' attribute can only be a byte, that is: int "
+ "or str, not %s" % (name, type(value)))
def _format_parts(self, fields):
"utility function to be used in __str__ and __repr__"
-
+
parts = []
for i in fields:
- parts.append( "%s=0x%02X" % (i, getattr(self, i)) )
-
+ parts.append("%s=0x%02X" % (i, getattr(self, i)))
+
return parts
-
+
def __str__(self):
- result = "%s(%s)" % (self.__class__.__name__, ", ".join(self._format_fields()))
-
+ result = "%s(%s)" % (self.__class__.__name__,
+ ", ".join(self._format_fields()))
+
if len(self.data) > 0:
result = result + " with %i (0x%02x) bytes of data" % (
- len(self.data), len(self.data)
+ len(self.data), len(self.data)
)
return result + ":\n" + hexdump(self.data)
else:
return result
-
+
def __repr__(self):
parts = self._format_fields()
-
+
if len(self.data) > 0:
parts.append("data=%r" % self.data)
-
+
return "%s(%s)" % (self.__class__.__name__, ", ".join(parts))
+
class C_APDU(APDU):
"Class for a command APDU"
-
+
def parse(self, apdu):
- "Parse a full command APDU and assign the values to our object, overwriting whatever there was."
- apdu = map( lambda a: (isinstance(a, str) and (ord(a),) or (a,))[0], apdu)
+ """Parse a full command APDU and assign the values to our object,
+ overwriting whatever there was."""
+ apdu = map(lambda a: (isinstance(a, str) and (ord(a),) or
+ (a,))[0], apdu)
apdu = apdu + [0] * max(4-len(apdu), 0)
-
- self.CLA, self.INS, self.P1, self.P2 = apdu[:4] # case 1, 2, 3, 4
+
+ self.CLA, self.INS, self.P1, self.P2 = apdu[:4] # case 1, 2, 3, 4
self.__extended_length = False
- if len(apdu) == 4: # case 1
+ if len(apdu) == 4: # case 1
self.data = ""
- elif (len(apdu) >= 7) and (apdu[4] == 0): # extended length apdu
+ elif (len(apdu) >= 7) and (apdu[4] == 0): # extended length apdu
self.__extended_length = True
- if len(apdu) == 7: # case 2 extended length
- self.Le = (apdu[-2]<<8) + apdu[-1]
+ if len(apdu) == 7: # case 2 extended length
+ self.Le = (apdu[-2] << 8) + apdu[-1]
self.data = ""
- else: # case 3, 4 extended length
- self.Lc = (apdu[5]<<8) + apdu[6]
- if len(apdu) == 7 + self.Lc: # case 3 extended length
+ else: # case 3, 4 extended length
+ self.Lc = (apdu[5] << 8) + apdu[6]
+ if len(apdu) == 7 + self.Lc: # case 3 extended length
self.data = apdu[7:]
- elif len(apdu) == 7 + self.Lc + 3: # case 4 extended length
- self.Le = (apdu[-2]<<8) + apdu[-1]
+ elif len(apdu) == 7 + self.Lc + 3: # case 4 extended length
+ self.Le = (apdu[-2] << 8) + apdu[-1]
self.data = apdu[7:-3]
- elif len(apdu) == 7 + self.Lc + 2 and apdu[-2:] == [0, 0]: # case 4 extended length with max le
+
+ # case 4 extended length with max le
+ elif len(apdu) == 7 + self.Lc + 2 and apdu[-2:] == [0, 0]:
self.Le = 0
self.data = apdu[7:-2]
else:
- raise ValueError("Invalid Lc value. Is %s, should be %s or %s"
- % ( self.Lc, 7 + self.Lc, 7 + self.Lc + 3))
+ raise ValueError("Invalid Lc value. Is %s, should be %s "
+ "or %s" % (self.Lc, 7 + self.Lc,
+ 7 + self.Lc + 3))
else: # short apdu
if len(apdu) == 5: # case 2 short apdu
self.Le = apdu[-1]
@@ -288,21 +306,28 @@ class C_APDU(APDU):
self.data = apdu[5:-1]
self.Le = apdu[-1]
else:
- raise ValueError("Invalid Lc value. Is %s, should be %s or %s" % (self.Lc,
- 5 + self.Lc, 5 + self.Lc + 1))
-
- CLA = _make_byte_property("CLA"); cla = CLA
- INS = _make_byte_property("INS"); ins = INS
- P1 = _make_byte_property("P1"); p1 = P1
- P2 = _make_byte_property("P2"); p2 = P2
- Lc = _make_byte_property("Lc"); lc = Lc
- Le = _make_byte_property("Le"); le = Le
-
+ raise ValueError("Invalid Lc value. Is %s, should be %s "
+ "or %s" % (self.Lc, 5 + self.Lc,
+ 5 + self.Lc + 1))
+
+ CLA = _make_byte_property("CLA")
+ cla = CLA
+ INS = _make_byte_property("INS")
+ ins = INS
+ P1 = _make_byte_property("P1")
+ p1 = P1
+ P2 = _make_byte_property("P2")
+ p2 = P2
+ Lc = _make_byte_property("Lc")
+ lc = Lc
+ Le = _make_byte_property("Le")
+ le = Le
+
@property
def effective_Le(self):
if hasattr(self, "_Le") and (self.Le == 0):
if self.__extended_length:
- return MAX_EXTENDED_LE
+ return MAX_EXTENDED_LE
else:
return MAX_SHORT_LE
else:
@@ -312,32 +337,34 @@ class C_APDU(APDU):
fields = ["CLA", "INS", "P1", "P2"]
if self.Lc > 0:
fields.append("Lc")
- if hasattr(self, "_Le"): ## There's a difference between "Le = 0" and "no Le"
+
+ # There's a difference between "Le = 0" and "no Le"
+ if hasattr(self, "_Le"):
fields.append("Le")
-
+
return self._format_parts(fields)
-
+
def render(self):
"Return this APDU as a binary string"
buffer = []
-
+
for i in self.CLA, self.INS, self.P1, self.P2:
buffer.append(chr(i))
-
+
if len(self.data) > 0:
buffer.append(chr(self.Lc))
buffer.append(self.data)
-
+
if hasattr(self, "_Le"):
if self.__extended_length:
buffer.append(chr(0x00))
- buffer.append(chr(self.Le>>8))
- buffer.append(chr(self.Le - self.Le>>8))
+ buffer.append(chr(self.Le >> 8))
+ buffer.append(chr(self.Le - self.Le >> 8))
else:
buffer.append(chr(self.Le))
-
+
return "".join(buffer)
-
+
def case(self):
"Return 1, 2, 3 or 4, depending on which ISO case we represent."
if self.Lc == 0:
@@ -351,34 +378,38 @@ class C_APDU(APDU):
else:
return 4
-
+
class R_APDU(APDU):
"Class for a response APDU"
-
- def _getsw(self): return chr(self.SW1) + chr(self.SW2)
+
+ def _getsw(self):
+ return chr(self.SW1) + chr(self.SW2)
+
def _setsw(self, value):
if len(value) != 2:
raise ValueError("SW must be exactly two bytes")
self.SW1 = value[0]
self.SW2 = value[1]
-
+
SW = property(_getsw, _setsw, None,
- "The Status Word of this response APDU")
+ "The Status Word of this response APDU")
sw = SW
-
- SW1 = _make_byte_property("SW1"); sw1 = SW1
- SW2 = _make_byte_property("SW2"); sw2 = SW2
-
+
+ SW1 = _make_byte_property("SW1")
+ sw1 = SW1
+ SW2 = _make_byte_property("SW2")
+ sw2 = SW2
+
def parse(self, apdu):
- "Parse a full response APDU and assign the values to our object, overwriting whatever there was."
+ """Parse a full response APDU and assign the values to our object,
+ overwriting whatever there was."""
self.SW = apdu[-2:]
self.data = apdu[:-2]
-
+
def _format_fields(self):
fields = ["SW1", "SW2"]
return self._format_parts(fields)
-
+
def render(self):
"Return this APDU as a binary string"
return self.data + self.sw
-