git-svn-id: https://vsmartcard.svn.sourceforge.net/svnroot/vsmartcard@14 96b47cad-a561-4643-ad3b-153ac7d7599c
1760 lines
67 KiB
Python
1760 lines
67 KiB
Python
#
|
|
# Copyright (C) 2009 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 <http://www.gnu.org/licenses/>.
|
|
#
|
|
"""
|
|
@todo use bertlv_pack for fdm
|
|
@todo zu lange daten abschneiden und trotzdem tlv laenge beibehalten
|
|
"""
|
|
from pickle import dumps, loads
|
|
from ConstantDefinitions import *
|
|
from TLVutils import *
|
|
from SWutils import *
|
|
from utils import stringtoint, inttostring
|
|
|
|
def isEqual(list): # {{{
|
|
"""Returns True, if all items are equal, otherwise False"""
|
|
if len(list) > 1:
|
|
for item in list:
|
|
if item != list[0]:
|
|
return False
|
|
|
|
return True
|
|
# }}}
|
|
|
|
def walk(start, path): # {{{
|
|
"""Walks a path of fids and returns the last file (EF or DF).
|
|
|
|
start -- DF, where to look for the first fid
|
|
path -- a string of fids
|
|
"""
|
|
if len(path) % 2 != 0:
|
|
raise SwError(SW["ERR_INCORRECTPARAMETERS"])
|
|
index = 0
|
|
while index < len(path):
|
|
if not isinstance(start, DF):
|
|
# File or application not found
|
|
raise SwError(SW["ERR_FILENOTFOUND"])
|
|
start = start.select('fid', stringtoint(path[:2]))
|
|
index = index + 2
|
|
|
|
return start
|
|
# }}}
|
|
|
|
def getfile_byrefdataobj(mf, refdataobjs): # {{{
|
|
"""Returns a list of files according to the given list of reference data
|
|
objects.
|
|
|
|
mf -- the MF
|
|
refdataobjs -- a list of 3-tuples (tag, length, newvalue)"""
|
|
|
|
result = [None]
|
|
for tag, length, newvalue in refdataobjs:
|
|
if tag != TAG["FILE_REFERENCE"]:
|
|
raise ValueError
|
|
|
|
if length == 0:
|
|
file = mf
|
|
|
|
elif length <= 2:
|
|
newvalue = stringtoint(newvalue)
|
|
if length == 1:
|
|
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)
|
|
|
|
else:
|
|
# length == 2:
|
|
file = mf.select('fid', newvalue)
|
|
|
|
else:
|
|
if length % 2 == 1:
|
|
raise NotImplementedError
|
|
else:
|
|
if newvalue[:2] == "\x3f\x00":
|
|
# absolute
|
|
file = walk(mf, newvalue[2:])
|
|
else:
|
|
# relative
|
|
file = walk(mf.currentDF(), newvalue[2:])
|
|
|
|
result.append(file)
|
|
|
|
return result
|
|
# }}}
|
|
|
|
def write(old, newlist, offsets, datacoding, maxsize=None): # {{{
|
|
"""Returns the status bytes and the result of a writeoperation 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 maxsize: the maximum number of bytes in the result
|
|
"""
|
|
result = old
|
|
listindex = 0
|
|
while listindex < len(offsets) and listindex < len(newlist):
|
|
offset = offsets[listindex]
|
|
new = newlist[listindex]
|
|
writenow = len(new)
|
|
|
|
if offset > len(result):
|
|
raise SwError(SW["ERR_OFFSETOUTOFFILE"])
|
|
if maxsize and offset + writenow > maxsize:
|
|
raise SwError(SW["ERR_NOTENOUGHMEMORY"], old)
|
|
|
|
if datacoding == DCB["ONETIMEWRITE"]:
|
|
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
|
|
resultindex = offset + newindex
|
|
while newindex < writenow:
|
|
if datacoding == DCB["WRITEOR"]:
|
|
newpiece = chr(
|
|
ord(result[resultindex]) | ord(new[newindex]))
|
|
elif datacoding == DCB["WRITEAND"]:
|
|
newpiece = chr(
|
|
ord(result[resultindex]) & ord(new[newindex]))
|
|
elif datacoding == DCB["PROPRIETARY"]:
|
|
# we use it for XOR
|
|
newpiece = chr(
|
|
ord(result[resultindex]) ^ ord(new[newindex]))
|
|
result = (result[0:resultindex] + newpiece +
|
|
result[resultindex+1:len(result)])
|
|
newindex = newindex + 1
|
|
resultindex = resultindex + 1
|
|
|
|
listindex = listindex + 1
|
|
|
|
return result
|
|
# }}}
|
|
|
|
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
|
|
"""
|
|
if (reference in [REF["IDENTIFIER_FIRST"],
|
|
REF["IDENTIFIER_LAST"]]
|
|
or index_current == -1):
|
|
# Read first occurence 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"]):
|
|
# Read last occurrence OR
|
|
# No current record and previous occurrence
|
|
indexes.reverse();
|
|
elif reference == REF["IDENTIFIER_PREVIOUS"]:
|
|
# Read previous occurrence
|
|
indexes = range(0, index_current)
|
|
indexes.reverse()
|
|
else:
|
|
# Read next occurrence
|
|
indexes = range(index_current + 1, len(items))
|
|
return indexes
|
|
# }}}
|
|
|
|
def prettyprint_anything(indent, thing): # {{{
|
|
"""
|
|
Returns a recursively generated string representation of an object and its
|
|
attributes.
|
|
"""
|
|
s = "%s{%s at 0x%x}:" % (indent, thing.__class__.__name__, id(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)
|
|
elif isinstance(newvalue, str):
|
|
s = s + "\n" + indent + attribute + (16-len(attribute))*" " + "length %d" % len(newvalue)
|
|
elif isinstance(newvalue, list):
|
|
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): # {{{
|
|
"""
|
|
Assigns a property to the calling object. This is used to decorate instance
|
|
variables with docstrings.
|
|
"""
|
|
return property(
|
|
lambda self: getattr(self, "_"+prop),
|
|
lambda self, value: setattr(self, "_"+prop, value),
|
|
lambda self: delattr(self, "_"+prop),
|
|
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")
|
|
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)")
|
|
def __init__(self, parent, fid, filedescriptor,
|
|
lifecycle=LCB["ACTIVATED"],
|
|
simpletlv_data=None,
|
|
bertlv_data=None,
|
|
SAM=None):
|
|
"""
|
|
The constructor is supposed to be involved by creation of a DF or EF.
|
|
"""
|
|
if (fid>0xFFFF or fid<0 or fid in [FID["EFDIR"], FID["EFATR"],
|
|
FID["PATHSELECTION"], FID["RESERVED"]] or filedescriptor>0xFF or
|
|
lifecycle>0xFF):
|
|
raise SwError(SW["ERR_INCORRECTPARAMETERS"])
|
|
self.lifecycle=lifecycle
|
|
self.parent = parent
|
|
if parent:
|
|
if parent.fid == fid:
|
|
raise SwError(SW["ERR_INCORRECTPARAMETERS"])
|
|
self.fid=fid
|
|
self.filedescriptor=filedescriptor
|
|
self.SAM = SAM
|
|
if simpletlv_data:
|
|
if not isinstance(simpletlv_data, list):
|
|
raise ValueError, "must be a list of (tag, length, value)-tuples"
|
|
self.setdec('simpletlv_data', simpletlv_data)
|
|
if bertlv_data:
|
|
if not isinstance(bertlv_data, list):
|
|
raise ValueError, "must be a list of (tag, length, value)-tuples"
|
|
self.setdec('bertlv_data', bertlv_data)
|
|
|
|
def decrypt(self,path, data):
|
|
if self.SAM == 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
|
|
return data
|
|
else:
|
|
return self.SAM.FSdecrypt(path,data)
|
|
|
|
def __str__(self):
|
|
"""Returns a string of the object using an prettyprint_anything."""
|
|
return prettyprint_anything("", self)
|
|
__repr__ = __str__
|
|
|
|
def getpath(self):
|
|
"""Returns the path to this file beginning with the MF's fid."""
|
|
if self.parent == None:
|
|
return inttostring(self.fid, 2)
|
|
else:
|
|
return self.parent.getpath() + inttostring(self.fid, 2)
|
|
|
|
def getenc(self, attribute):
|
|
"""Returns the decrypted data of an 'attribute' from the file."""
|
|
return loads(self.decrypt(self.getpath(), getattr(self, attribute)))
|
|
|
|
def setdec(self, attribute, data):
|
|
"""Encrypts 'data' and sets the file's 'attribute' to the chiffre."""
|
|
setattr(self, attribute, dumps(self.encrypt(self.getpath(), data)))
|
|
|
|
def getdata(self, isSimpleTlv, requestedTL):
|
|
"""
|
|
Returns a string of either the file's BER-TLV or the file's SIMPLE-TLV
|
|
coded data objects depending on the bool 'isSimpleTlv'. 'requestedTL'
|
|
is a list of (tag, length)-tuples that specify which tags should be
|
|
returned in what size.
|
|
"""
|
|
if isSimpleTlv:
|
|
attribute = 'simpletlv_data'
|
|
else:
|
|
attribute = 'bertlv_data'
|
|
|
|
if not hasattr(self, attribute):
|
|
raise SwError(SW["ERR_NOTSUPPORTED"])
|
|
|
|
tlv_data = self.getenc(attribute)
|
|
if requestedTL == []:
|
|
result = tlv_data
|
|
else:
|
|
result = []
|
|
for T, L in requestedTL:
|
|
tagfound = False
|
|
for i in range(0, len(tlv_data)):
|
|
tag, _, value = tlv_data[i]
|
|
if T == tag:
|
|
tagfound = True
|
|
if L == 0:
|
|
result.append(tlv_data[i])
|
|
else:
|
|
result.append((T, L, value[:L]))
|
|
if not tagfound:
|
|
raise SwError(SW["ERR_DATANOTFOUND"])
|
|
|
|
if isSimpleTlv:
|
|
return simpletlv_pack(result)
|
|
return bertlv_pack(result)
|
|
|
|
def putdata(self, isSimpleTlv, newtlvlist):
|
|
"""
|
|
Sets either the file's BER-TLV or the file's SIMPLE-TLV coded data
|
|
objects depending on the bool 'isSimpleTlv'. 'newtlvlist' is a list of
|
|
(tag, length, value)-tuples of new data.
|
|
"""
|
|
if isSimpleTlv:
|
|
attribute = 'simpletlv_data'
|
|
else:
|
|
attribute = 'bertlv_data'
|
|
|
|
if not hasattr(self, attribute):
|
|
raise SwError(SW["ERR_NOTSUPPORTED"])
|
|
|
|
tlv_data = self.getenc(attribute)
|
|
for tag, newlength, newvalue in newtlvlist:
|
|
tagfound = False
|
|
for i in range(0, len(tlv_data)):
|
|
t, oldlength, oldvalue = tlv_data[i]
|
|
if t == tag:
|
|
# TODO: what if multiple tags can be found?
|
|
value = write(oldvalue, [newvalue], [0], newlength,
|
|
self.datacoding)
|
|
tlv_data[i] = (tag, len(value), value)
|
|
tagfound = True
|
|
if not tagfound:
|
|
tlv_data.append(tag, newlength, newvalue)
|
|
self.setdec(attribute, tlv_data)
|
|
|
|
def readbinary(*argz, **args):
|
|
"""Only a template, will raise an error."""
|
|
raise SwError(SW["ERR_INCOMPATIBLEWITHFILE"])
|
|
|
|
def writebinary(*argz, **args):
|
|
"""Only a template, will raise an error."""
|
|
raise SwError(SW["ERR_INCOMPATIBLEWITHFILE"])
|
|
|
|
def updatebinary(*argz, **args):
|
|
"""Only a template, will raise an error."""
|
|
raise SwError(SW["ERR_INCOMPATIBLEWITHFILE"])
|
|
|
|
def erasebinary(*argz, **args):
|
|
"""Only a template, will raise an error."""
|
|
raise SwError(SW["ERR_INCOMPATIBLEWITHFILE"])
|
|
|
|
def readrecord(*argz, **args):
|
|
"""Only a template, will raise an error."""
|
|
raise SwError(SW["ERR_INCOMPATIBLEWITHFILE"])
|
|
|
|
def writerecord(*argz, **args):
|
|
"""Only a template, will raise an error."""
|
|
raise SwError(SW["ERR_INCOMPATIBLEWITHFILE"])
|
|
|
|
def appendrecord(*argz, **args):
|
|
"""Only a template, will raise an error."""
|
|
raise SwError(SW["ERR_INCOMPATIBLEWITHFILE"])
|
|
|
|
def updaterecord(*argz, **args):
|
|
"""Only a template, will raise an error."""
|
|
raise SwError(SW["ERR_INCOMPATIBLEWITHFILE"])
|
|
|
|
def select(*argz, **args):
|
|
"""Only a template, will raise an error."""
|
|
raise SwError(SW["ERR_INCOMPATIBLEWITHFILE"])
|
|
# }}}
|
|
|
|
|
|
class DF(File): # {{{
|
|
"""Class for a dedicated file"""
|
|
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=""):
|
|
"""
|
|
See File for more.
|
|
"""
|
|
File.__init__(self, parent, fid, filedescriptor, lifecycle,
|
|
simpletlv_data, bertlv_data)
|
|
if dfname:
|
|
if len(dfname)>16:
|
|
raise SwError(SW["ERR_INCORRECTPARAMETERS"])
|
|
self.dfname=dfname
|
|
self.content = []
|
|
# TODO: opensc sends the length of data, but what does it limit
|
|
# (bertlv-data/simpletlv-data/number of files in DF, ...)?
|
|
self.setdec('data', data)
|
|
|
|
def __len__(self):
|
|
"""
|
|
x.__len__() <==> len(x.content)
|
|
"""
|
|
return len(self.content)
|
|
|
|
def __getitem__(self, key):
|
|
"""
|
|
x.__getitem__(key) <==> x.content[key]
|
|
"""
|
|
return self.content[key]
|
|
|
|
def __setitem__(self, key, value):
|
|
"""
|
|
x.__setitem__(key, value) <==> x.content[key]=value
|
|
"""
|
|
return self.content[key]
|
|
|
|
def __delitem__(self, key):
|
|
"""
|
|
x.__delitem__(key) <==> del x.content[key]
|
|
"""
|
|
del content[key]
|
|
|
|
def __contains__(self, item):
|
|
"""
|
|
x.__contains__(item) <==> item in x.content
|
|
"""
|
|
return item in content
|
|
|
|
def append(self, file):
|
|
"""Appends 'file' to the content of the DF."""
|
|
if not (isinstance(file, DF) or isinstance(file, EF)):
|
|
raise TypeError
|
|
|
|
if (self.fid == file.fid or file.fid == FID["MF"] or file.fid ==
|
|
FID["RESERVED"] or file.fid == FID["PATHSELECTION"]):
|
|
raise SwError(SW["ERR_INCORRECTPARAMETERS"])
|
|
|
|
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:
|
|
raise SwError(SW["ERR_DFNAMEEXISTS"])
|
|
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):
|
|
"""
|
|
Returns the first file of the DF, that has the 'attribute' with the
|
|
specified 'value'. For partial DF name selection you must specify the
|
|
first/last/next or previous occurence with 'reference' and the index of
|
|
the current file 'index_current' (-1 for None).
|
|
"""
|
|
indexes = get_indexes(self.content, reference, index_current)
|
|
|
|
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))):
|
|
return file
|
|
# not found
|
|
if isinstance(value, int):
|
|
print "file not found %s=%x" % (attribute, value)
|
|
elif isinstance(value, str):
|
|
print "file not found %s=%r" % (attribute, value)
|
|
raise SwError(SW["ERR_FILENOTFOUND"])
|
|
|
|
def remove(self, file):
|
|
"""Removes 'file' from the content of the DF"""
|
|
content.remove(file)
|
|
# }}}
|
|
|
|
|
|
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):
|
|
"""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)
|
|
self.secondSFT = inttostring(MF.makeSecondSoftwareFunctionTable(), 1)
|
|
|
|
|
|
@staticmethod
|
|
def makeFirstSoftwareFunctionTable(# {{{
|
|
DFSelectionByFullDFName=True, DFSelectionByPartialDFName=True,
|
|
DFSelectionByPath=True, DFSelectionByFID=True,
|
|
DFSelectionByApplication_implicite=True, ShortFIDSupported=True,
|
|
RecordNumberSupported=True, RecordIdentifierSupported=True):
|
|
"""
|
|
Returns a byte according to the first software function table from the
|
|
historical bytes of the card capabilities.
|
|
|
|
DFSelectionByFullDFName -- Bool (True).
|
|
DFSelectionByPartialDFName -- Bool (True).
|
|
DFSelectionByPath -- Bool (True).
|
|
DFSelectionByFID -- Bool (True).
|
|
DFSelectionByApplication_implicite -- Bool (True).
|
|
ShortFIDSupported -- Bool (True).
|
|
RecordNumberSupported -- Bool (True).
|
|
RecordIdentifierSupported -- Bool (True).
|
|
"""
|
|
fsft = 0
|
|
if DFSelectionByFullDFName:
|
|
fsft |= 1 << 7
|
|
if DFSelectionByPartialDFName:
|
|
fsft |= 1 << 6
|
|
if DFSelectionByPath:
|
|
fsft |= 1 << 5
|
|
if DFSelectionByFID:
|
|
fsft |= 1 << 4
|
|
if DFSelectionByApplication_implicite:
|
|
fsft |= 1 << 3
|
|
if ShortFIDSupported:
|
|
fsft |= 1 << 2
|
|
if RecordNumberSupported:
|
|
fsft |= 1 << 1
|
|
if RecordIdentifierSupported:
|
|
fsft |= 1
|
|
return fsft
|
|
# }}}
|
|
|
|
@staticmethod
|
|
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):
|
|
return self.current
|
|
else:
|
|
return None
|
|
|
|
@staticmethod
|
|
def encodeFileControlParameter(file):
|
|
"""
|
|
Returns a string of TLV-coded file control information of 'file'. Note:
|
|
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) ]
|
|
|
|
# TODO filesize and data objects
|
|
if isinstance(file, EF):
|
|
if hasattr(file, 'shortfid'):
|
|
fdm.append("%c\x01%c" % (TAG["SHORTFID"], file.shortfid))
|
|
else:
|
|
fdm.append("%c\x00" % TAG["SHORTFID"])
|
|
|
|
if isinstance(file, TransparentStructureEF):
|
|
l = inttostring(len(file.getenc('data')))
|
|
fdm.append("%c%c%s" % (TAG["BYTES_EXCLUDINGSTRUCTURE"],
|
|
chr(len(l)), l))
|
|
fdm.append("%c%c%s" % (TAG["BYTES_INCLUDINGSTRUCTURE"],
|
|
chr(len(l)), l))
|
|
fdm.append("%c\x02%c%c" % (TAG["FILEDISCRIPTORBYTE"],
|
|
file.filedescriptor, file.datacoding))
|
|
|
|
elif isinstance(file, RecordStructureEF):
|
|
l = 0
|
|
records = file.getenc('records')
|
|
for r in records:
|
|
if file.hasSimpleTlv():
|
|
l += simpletlv_unpack(r.data)[0][1]
|
|
else:
|
|
l += len(r.data)
|
|
fdm.append("%c\x02%s" % (TAG["BYTES_EXCLUDINGSTRUCTURE"],
|
|
inttostring(l, 2)))
|
|
fdm.append("%c\x02%s" % (TAG["BYTES_INCLUDINGSTRUCTURE"],
|
|
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)))
|
|
|
|
elif isinstance(file, DF):
|
|
# TODO number of files == number of data bytes?
|
|
fdm.append("%c\x01%c" % (TAG["FILEDISCRIPTORBYTE"],
|
|
file.filedescriptor))
|
|
if hasattr(file, 'dfname'):
|
|
fdm.append("%c%c%s" % (TAG["DFNAME"], len(file.dfname),
|
|
file.dfname))
|
|
|
|
else:
|
|
raise TypeError
|
|
|
|
return "".join(fdm)
|
|
|
|
def _selectFile(self, p1, p2, data):
|
|
"""
|
|
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
|
|
|
|
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
|
|
# the command if the file to select is the MF, a DF or an EF.
|
|
if data[:2] == inttostring(self.fid):
|
|
selected = walk(self, data[2:])
|
|
elif data[:2] == inttostring(self.currentDF().fid):
|
|
selected = walk(self.currentDF(), data[2:])
|
|
else:
|
|
selected = walk(self.currentDF(), data)
|
|
elif p1 == P1_CHILD_DF or p1 == P1_CHILD_EF:
|
|
selected = self.select('fid', stringtoint(data))
|
|
if ((p1 == P1_CHILD_DF and not isinstance(selected, DF)) or
|
|
(p1 == P1_CHILD_EF and not isinstance(selected, EF))):
|
|
# Command incompatible with file structure
|
|
raise SwError(SW["ERR_INCOMPATIBLEWITHFILE"])
|
|
elif p1 == P1_PATH_FROM_MF:
|
|
selected = walk(self, data)
|
|
elif p1 == P1_PATH_FROM_CURRENTDF:
|
|
selected = walk(self.currentDF(), data)
|
|
elif p1 == P1_PARENT_DF:
|
|
selected = self.current.parent
|
|
elif p1 == P1_DF_NAME:
|
|
df = self.currentDF()
|
|
if df == self or df not in self.content:
|
|
index_current = -1
|
|
else:
|
|
index_current = self.content.index(df)
|
|
selected = self.select('dfname', data, p2 &
|
|
REF["REFERENCE_CONTROL"], index_current)
|
|
else:
|
|
print "unknown selection method: p1 =", p1
|
|
selected = None
|
|
|
|
if selected == None:
|
|
raise SwError(SW["ERR_FILENOTFOUND"])
|
|
|
|
return selected
|
|
|
|
def selectFile(self, p1, p2, data):
|
|
"""
|
|
Function for instruction 0xa4. 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.
|
|
"""
|
|
P2_FCI = 0
|
|
P2_FCP = 1<<2
|
|
P2_FMD = 2<<2
|
|
P2_NONE = 3<<2
|
|
file = self._selectFile(p1, p2, data)
|
|
|
|
if p2 == P2_NONE:
|
|
data = ""
|
|
elif p2 == P2_FMD:
|
|
# TODO
|
|
data = ""
|
|
else:
|
|
if p2 == P2_FCP:
|
|
tag = TAG["FILECONTROLPARAMETERS"]
|
|
else:
|
|
tag = TAG["FILECONTROLINFORMATION"]
|
|
fdm = MF.encodeFileControlParameter(file)
|
|
data = bertlv_pack([(tag, len(fdm), fdm)])
|
|
|
|
self.current = file
|
|
|
|
return SW["NORMAL"], data
|
|
|
|
def dataUnitsDecodePlain(self, p1, p2, data):
|
|
"""
|
|
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.
|
|
"""
|
|
if p1 >> 7:
|
|
# If bit 1 of INS is set to 0 and bit 8 of P1 to 1, then bits 7
|
|
# and 6 of P1 are set to 00 (RFU), bits 5 to 1 of P1 encode a
|
|
# short EF identifier and P2 (eight bits) encodes an offset
|
|
# from zero to 255.
|
|
ef = self.currentDF().select('shortfid', p1 & 0x1f)
|
|
self.current = ef
|
|
offsets = [p2]
|
|
else:
|
|
# If bit 1 of INS is set to 0 and bit 8 of P1 to 0, then P1-P2
|
|
# (fifteen bits) encodes an offset from zero to 32 767.
|
|
ef = self.currentEF()
|
|
if not ef:
|
|
raise SwError(SW["ERR_NOCURRENTEF"])
|
|
offsets = [(p1 << 8) + p2]
|
|
|
|
return ef, offsets, [data]
|
|
|
|
def dataUnitsDecodeEncapsulated(self, p1, p2, data):
|
|
"""
|
|
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.
|
|
"""
|
|
# If bit 1 of INS is set to 1, then P1-P2 shall identify an EF. If
|
|
# the first eleven bits of P1-P2 are set to 0 and if bits 5 to 1 of
|
|
# P2 are not all equal and if the card and / or the EF supports
|
|
# selection by short EF identifier, then bits 5 to 1 of P2 encode a
|
|
# short EF identifier (a number from one to thirty). Otherwise,
|
|
# P1-P2 is a file identifier. P1-P2 set to '0000' identifies the
|
|
# current EF. At least one offset data object with tag '54' shall
|
|
# be present in the command data field. When present in a command
|
|
# 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)
|
|
datalist = decodeDiscretionaryDataObjects(tlv_data)
|
|
|
|
if p1 == 0 and p2 >> 5 == 0:
|
|
ef = self.currentDF().select('shortfid', p2)
|
|
else:
|
|
ef = self.currentDF().select('fid', p1 << 8 + p1)
|
|
self.current = ef
|
|
|
|
return ef, offsets, datalist
|
|
|
|
def readBinaryPlain(self, p1, p2, data):
|
|
"""
|
|
Function for instruction 0xb0. 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, offsets, datalist = self.dataUnitsDecodePlain(p1, p2, data)
|
|
result = ef.readbinary(offsets[0])
|
|
|
|
return SW["NORMAL"], result
|
|
|
|
def readBinaryEncapsulated(self, p1, p2, data):
|
|
"""
|
|
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.
|
|
"""
|
|
ef, offsets, datalist = self.dataUnitsDecodeEncapsulated(p1, p2, data)
|
|
result = ef.readbinary(offsets[0])
|
|
|
|
r = encodeDiscretionaryDataObjects([result])
|
|
|
|
return SW["NORMAL"], r
|
|
|
|
def writeBinaryPlain(self, p1, p2, data):
|
|
"""
|
|
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.
|
|
"""
|
|
ef, offsets, datalist = self.dataUnitsDecodePlain(p1, p2, data)
|
|
ef.writebinary(offsets, datalist)
|
|
|
|
return SW["NORMAL"], ""
|
|
|
|
def writeBinaryEncapsulated(self, p1, p2, data):
|
|
"""
|
|
Function for instruction 0xd1. 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, offsets, datalist = self.dataUnitsDecodeEncapsulated(p1, p2, data)
|
|
ef.writebinary(offsets, datalist)
|
|
|
|
return SW["NORMAL"], ""
|
|
|
|
def updateBinaryPlain(self, p1, p2, data):
|
|
"""
|
|
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.
|
|
"""
|
|
ef, offsets, datalist = self.dataUnitsDecodePlain(p1, p2, data)
|
|
ef.updatebinary(offsets, datalist)
|
|
|
|
return SW["NORMAL"], ""
|
|
|
|
def updateBinaryEncapsulated(self, p1, p2, data):
|
|
"""
|
|
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.
|
|
"""
|
|
ef, offsets, datalist = self.dataUnitsDecodeEncapsulated(p1, p2, data)
|
|
ef.updatebinary(offsets, datalist)
|
|
|
|
return SW["NORMAL"], ""
|
|
|
|
def searchBinaryPlain(self, p1, p2, data):
|
|
ef, offsets, datalist = self.dataUnitsDecodePlain(p1, p2, data)
|
|
r = self.data.find(datalist[0], offsets[0])
|
|
if r == -1:
|
|
raise SwError(SW["ERR_DATANOTFOUND"])
|
|
|
|
return SW["NORMAL"], inttostring(r)
|
|
|
|
def searchBinaryEncapsulated(self, p1, p2, data):
|
|
ef, offsets, datalist = self.dataUnitsDecodeEncapsulated(p1, p2, data)
|
|
if len(offsets) != len(datalist):
|
|
raise SwError(SW["ERR_INCORRECTPARAMETERS"])
|
|
|
|
result = []
|
|
for data, offset in offsets, datalist:
|
|
r = self.data.find(data, offset)
|
|
if r == -1:
|
|
raise SwError(SW["ERR_DATANOTFOUND"])
|
|
result.append(inttostring(r))
|
|
|
|
return SW["NORMAL"], encodeDataOffsetObjects(result)
|
|
|
|
def eraseBinaryPlain(self, p1, p2, data):
|
|
"""
|
|
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.
|
|
"""
|
|
ef, offsets, datalist = self.dataUnitsDecodePlain(p1, p2, data)
|
|
# If INS = '0E', then, if present, the command data field encodes
|
|
# the offset of the first data unit not to be erased. This offset
|
|
# shall be higher than the one encoded in P1-P2. If the data field
|
|
# is absent, then the command erases up to the end of the file.
|
|
erasefrom = offsets[0]
|
|
|
|
if data:
|
|
tlv_data = bertlv_unpack(data)
|
|
eraseto = decodeOffsetDataObjects(tlv_data)[0]
|
|
else:
|
|
eraseto = None
|
|
|
|
ef.erasebinary(erasefrom, eraseto)
|
|
return SW["NORMAL"], ""
|
|
|
|
def eraseBinaryEncapsulated(self, p1, p2, data):
|
|
"""
|
|
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.
|
|
"""
|
|
ef, offsets, datalist = self.dataUnitsDecodeEncapsulated(p1, p2, data)
|
|
# If INS = '0F', then, if present, the command data field shall
|
|
# consist of zero, one or two offset data objects. If there is no
|
|
# offset, then the command erases all the data units in the file.
|
|
# If there is one offset, it indicates the first data unit to be
|
|
# erased; then the command erases up to the end of the file. Two
|
|
# offsets define a sequence of data units: the second offset
|
|
# indicates the first data unit not to be erased; it shall be
|
|
# higher than the first offset.
|
|
if len(offsets) > 1:
|
|
eraseto = offsets[1]
|
|
else:
|
|
eraseto = None
|
|
|
|
if len(offsets) > 0:
|
|
erasefrom = offsets[0]
|
|
else:
|
|
erasefrom = None
|
|
|
|
ef.erasebinary(erasefrom, eraseto)
|
|
return SW["NORMAL"], ""
|
|
|
|
def recordHandlingDecode(self, p1, p2):
|
|
"""
|
|
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 (i. e. the last 3 bits
|
|
of 'p1').
|
|
"""
|
|
if p1 == 0xff:
|
|
# RFU
|
|
raise SwError(SW["ERR_INCORRECTP1P2"])
|
|
else:
|
|
num_id = p1
|
|
|
|
shortfid = p2 >> 3
|
|
if shortfid == 0:
|
|
ef = self.currentEF()
|
|
if ef == None:
|
|
raise SwError(SW["ERR_NOCURRENTEF"])
|
|
elif shortfid == 0x1f:
|
|
# RFU
|
|
raise SwError(SW["ERR_INCORRECTP1P2"])
|
|
else:
|
|
ef = self.currentDF().select('shortfid', shortfid)
|
|
self.current = ef
|
|
ef.resetRecordPointer()
|
|
|
|
return ef, num_id, p2 & REF["REFERENCE_CONTROL"]
|
|
|
|
def readRecordPlain(self, p1, p2, data):
|
|
"""
|
|
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.
|
|
"""
|
|
ef, num_id, reference = self.recordHandlingDecode(p1, p2)
|
|
result = ef.readrecord(0, num_id, reference)
|
|
|
|
r = ""
|
|
for item in result:
|
|
r += item
|
|
|
|
return SW["NORMAL"], r
|
|
|
|
def readRecordEncapsulated(self, p1, p2, data):
|
|
"""
|
|
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.
|
|
"""
|
|
ef, num_id, reference = self.recordHandlingDecode(p1, p2)
|
|
result = ef.readrecord(0, num_id, reference)
|
|
|
|
return SW["NORMAL"], encodeDiscretionaryDataObjects(result)
|
|
|
|
def writeRecord(self, p1, p2, data):
|
|
"""
|
|
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"]]:
|
|
# RFU
|
|
raise SwError(SW["ERR_INCORRECTPARAMETERS"])
|
|
ef.writerecord(num_id, reference, 1, data)
|
|
|
|
return SW["NORMAL"], ""
|
|
|
|
def updateRecordPlain(self, p1, p2, data):
|
|
"""
|
|
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"]]:
|
|
# RFU
|
|
raise SwError(SW["ERR_INCORRECTPARAMETERS"])
|
|
ef.updaterecord(num_id, reference, 0, data)
|
|
|
|
return SW["NORMAL"], ""
|
|
|
|
def updateRecordEncapsulated(self, p1, p2, data):
|
|
"""
|
|
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
|
|
tlv_data = bertlv_unpack(data)
|
|
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"])
|
|
elif reference == P2_AND:
|
|
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"])
|
|
else:
|
|
# reference == P2_XOR:
|
|
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. Returns the status bytes as two
|
|
byte long integer and the response data as binary string.
|
|
"""
|
|
if p1 != 0 or (p2 & REF["REFERENCE_CONTROL"]) != 0:
|
|
raise SwError(SW["ERR_INCORRECTP1P2"])
|
|
ef, num_id, reference = self.recordHandlingDecode(p1, p2)
|
|
sw = ef.appendrecord(data)
|
|
|
|
return SW["NORMAL"], ""
|
|
|
|
def eraseRecord(self, p1, p2, data):
|
|
"""
|
|
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.
|
|
"""
|
|
ef, num_id, reference = self.recordHandlingDecode(p1, p2)
|
|
if reference not in [
|
|
REF["NUMBER"],
|
|
REF["NUMBER_TO_LAST"]]:
|
|
# RFU
|
|
raise SwError(SW["ERR_INCORRECTPARAMETERS"])
|
|
ef.eraserecord(num_id, reference)
|
|
|
|
return SW["NORMAL"], ""
|
|
|
|
def dataObjectHandlingDecodePlain(self, p1, p2, data):
|
|
"""
|
|
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:
|
|
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 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)]
|
|
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
|
|
# 'FE' shall be a SIMPLE-TLV tag.
|
|
tlv_data = [(p2, len(data), data)]
|
|
isSimpleTlv = True
|
|
elif p1 == 0x00 and p2 == 0xff:
|
|
# The value '00FF' is used either for obtaining all the common
|
|
# BER-TLV data objects readable in the context, or for indicating
|
|
# that the command data field is encoded in BER-TLV.
|
|
if data:
|
|
tlv_data = bertlv_unpack(data)
|
|
else:
|
|
tlv_data = []
|
|
isSimpleTlv = False
|
|
elif p1 == 0x02 and p2 == 0xff:
|
|
# The value '02FF' is used either for obtaining all the common
|
|
# SIMPLE-TLV data objects readable in the context or for indicating
|
|
# that the command data field is encoded in SIMPLE-TLV.
|
|
if data:
|
|
tlv_data = simpletlv_unpack(data)
|
|
else:
|
|
tlv_data = []
|
|
isSimpleTlv = True
|
|
else:
|
|
raise SwError(SW["ERR_NOTSUPPORTED"])
|
|
|
|
return file, isSimpleTlv, tlv_data
|
|
|
|
def dataObjectHandlingDecodeEncapsulated(self, p1, p2, data):
|
|
"""
|
|
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 SIMPLE-TLV data objects False
|
|
otherwise and a list of (tag, length, value)-tuples.
|
|
"""
|
|
# If bit 1 of INS is set to 1, then P1-P2 shall identify a file.
|
|
tlv_data = bertlv_unpack(data)
|
|
if p1 == 0 and p2 == 0:
|
|
# P1-P2 set to '0000' identifies the current EF, unless the command
|
|
# 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:
|
|
file = self.currentEF()
|
|
if file == 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
|
|
# of P2 are not all equal and if the card and / or the file
|
|
# supports selection by short EF identifier, then bits 5 to 1 of P2
|
|
# encode a short EF identifier (a number from one to thirty).
|
|
file = self.currentDF().select('shortfid', p2)
|
|
elif p1 == 0x3f and p2 == 0xff:
|
|
# P1-P2 set to '3FFF' identifies the current DF.
|
|
file = self.currentDF()
|
|
else:
|
|
# Otherwise, P1-P2 is a file identifier.
|
|
file = self.currentDF().select('fid', p1<<8 + p2)
|
|
|
|
if file == None:
|
|
raise SwError(SW["ERR_FILENOTFOUND"])
|
|
self.current = file
|
|
|
|
return file, tlv_data
|
|
|
|
def getDataPlain(self, p1, p2, data):
|
|
"""
|
|
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)
|
|
# TODO oversized answers
|
|
if len(tlvlist) > 0:
|
|
return SW["NORMAL"], file.getdata(isSimpleTlv, [(tlvlist[0][0], 0)])
|
|
else:
|
|
return SW["NORMAL"], file.getdata(isSimpleTlv, [])
|
|
|
|
def getDataEncapsulated(self, p1, p2, data):
|
|
"""
|
|
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)
|
|
# TODO oversized answers
|
|
requestedTL = decodeTagList(tlv_data)
|
|
if requestedTL == []:
|
|
requestedTL = decodeHeaderList(tlv_data)
|
|
if requestedTL == []:
|
|
requestedTL = decodeExtendedHeaderList(tlv_data)
|
|
|
|
return SW["NORMAL"], file.getdata(False, requestedTL)
|
|
|
|
def putDataPlain(self, p1, p2, data):
|
|
"""
|
|
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.putdata(isSimpleTlv, tlvlist)
|
|
|
|
return SW["NORMAL"], ""
|
|
|
|
def putDataEncapsulated(self, p1, p2, data):
|
|
"""
|
|
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.
|
|
"""
|
|
file, tlvlist = self.dataObjectHandlingDecodeEncapsulated(p1, p2, data)
|
|
file.putdata(False, tlvlist)
|
|
|
|
return SW["NORMAL"], ""
|
|
|
|
|
|
@staticmethod
|
|
def create(p1, p2, data):
|
|
"""
|
|
Creates and returns a list of files according to the parameters of a
|
|
create file command.
|
|
"""
|
|
def fdb2args(value, args):
|
|
l = len(value)
|
|
if l >= 5:
|
|
# TODO number of records on one or two bytes
|
|
raise SwError(SW["ERR_NOTSUPPORTED"])
|
|
if l >= 3:
|
|
args["maxrecordsize"] = stringtoint(value[2:])
|
|
if l >= 2:
|
|
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):
|
|
print "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)',
|
|
}
|
|
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 }
|
|
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"]:
|
|
raise ValueError
|
|
for tag, _, value in tlv_data:
|
|
exec tag2cmd.get(tag, 'unknown(tag, value)') in locals(), globals()
|
|
|
|
if (args["filedescriptor"] & FDB["DF"]) == FDB["DF"]:
|
|
# FIXME: data for DF
|
|
if "data" in args:
|
|
print 'what to do with DF-data %r?' % args["data"]
|
|
del args["data"]
|
|
file = DF(**args)
|
|
elif ((args["filedescriptor"] & 7) in
|
|
[FDB["EFSTRUCTURE_NOINFORMATIONGIVEN"],
|
|
FDB["EFSTRUCTURE_TRANSPARENT"]]):
|
|
file = TransparentStructureEF(**args)
|
|
file.writebinary( decodeOffsetDataObjects(tlv_data),
|
|
decodeDiscretionaryDataObjects(tlv_data) )
|
|
else:
|
|
file = RecordStructureEF(**args)
|
|
|
|
files.append(file)
|
|
|
|
return files
|
|
|
|
def createFile(self, p1, p2, data):
|
|
"""
|
|
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:
|
|
raise SwError(SW["ERR_NOCURRENTEF"])
|
|
|
|
for file in self.create(p1, p2, data):
|
|
file.parent = df
|
|
df.append(file)
|
|
self.current = file
|
|
|
|
return SW["NORMAL"], ""
|
|
|
|
def deleteFile(self, p1, p2, data):
|
|
"""
|
|
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
|
|
|
|
return SW["NORMAL"], ""
|
|
# }}}
|
|
|
|
|
|
class EF(File): # {{{
|
|
"""Template class for an elementary file."""
|
|
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):
|
|
"""
|
|
The constructor is supposed to be involved creation of a by creation of
|
|
a TransparentStructureEF or RecordStructureEF.
|
|
|
|
See File for more.
|
|
"""
|
|
# exlcude FIDs for DFs
|
|
if fid == FID["MF"] or datacoding>0xFF:
|
|
raise SwError(SW["ERR_INCORRECTPARAMETERS"])
|
|
if shortfid:
|
|
if not (1 <= shortfid and shortfid <= 30):
|
|
raise SwError(SW["ERR_INCORRECTPARAMETERS"])
|
|
self.shortfid=shortfid
|
|
File.__init__(self, parent, fid, filedescriptor, lifecycle,
|
|
simpletlv_data,
|
|
bertlv_data)
|
|
self.datacoding=datacoding
|
|
# }}}
|
|
|
|
|
|
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=""):
|
|
"""
|
|
See EF for more.
|
|
"""
|
|
EF.__init__(self, parent, fid,
|
|
filedescriptor, lifecycle,
|
|
simpletlv_data, bertlv_data,
|
|
datacoding, shortfid)
|
|
self.setdec('data', data)
|
|
|
|
def readbinary(self, offset):
|
|
"""Returns the string of decrypted data beginning at 'offset'."""
|
|
data = self.getenc('data')
|
|
|
|
if offset > len(data):
|
|
raise SwError(SW["ERR_OFFSETOUTOFFILE"])
|
|
|
|
return data[offset:]
|
|
|
|
def writebinary(self, offsets, datalist, datacoding=None):
|
|
"""
|
|
Writes pieces of data to the specified offsets honoring the given
|
|
coding byte.
|
|
|
|
offsets -- list of integers. Offsets.
|
|
datalist -- list of strings. Data pieces.
|
|
datacoding -- the data coding byte to use for writing
|
|
"""
|
|
data = self.getenc('data')
|
|
if datacoding:
|
|
data = write(data, datalist, offsets, datacoding)
|
|
else:
|
|
data = write(data, datalist, offsets, self.datacoding)
|
|
self.setdec('data', data)
|
|
|
|
def updatebinary(self, offsets, datalist):
|
|
"""
|
|
x.updatebinary(offsets, datalist) <==> x.writebinary(offsets, datalist, DCB["ONETIMEWRITE"])
|
|
"""
|
|
return self.writebinary(offsets, datalist, DCB["ONETIMEWRITE"])
|
|
|
|
def erasebinary(self, erasefrom, eraseto):
|
|
"""
|
|
Sets (part of) the content of an EF to its logical erased state,
|
|
sequentially starting from 'erasefrom' ending at 'eraseto'.
|
|
"""
|
|
data = self.getenc('data')
|
|
if erasefrom == None:
|
|
erasefrom = 0
|
|
if eraseto == None:
|
|
eraseto = len(data)
|
|
|
|
if erasefrom>len(data):
|
|
raise SwError(SW["ERR_OFFSETOUTOFFILE"])
|
|
if erasefrom>eraseto:
|
|
raise SwError(SW["ERR_INCORRECTPARAMETERS"])
|
|
|
|
data=data[0:erasefrom] + data[eraseto:len(data)]
|
|
self.setdec('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 for a Record of an elementary of record structure"""
|
|
def __init__(self, identifier=None, data=""):
|
|
"""
|
|
The constructor is supposed to be involved by EF.appendrecord.
|
|
"""
|
|
if identifier:
|
|
if identifier<0x01 or identifier>0xFE:
|
|
raise SwError(SW["ERR_INCORRECTPARAMETERS"])
|
|
self.identifier=identifier
|
|
self.data=data
|
|
|
|
def __str__(self):
|
|
"""Returns a string of the object using an prettyprint_anything."""
|
|
return prettyprint_anything("", self)
|
|
__repr__ = __str__
|
|
# }}}
|
|
|
|
|
|
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).")
|
|
def __init__(self, parent, fid, filedescriptor,
|
|
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.
|
|
linear/cyclic or variable/fixed EF). The record pointer is reset.
|
|
|
|
records -- list of Records
|
|
maxrecordsize -- integer. maximum length of a record's data.
|
|
|
|
See EF for more.
|
|
"""
|
|
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)
|
|
for r in records:
|
|
if len(r.data) > maxrecordsize or (self.hasFixedRecordSize() and
|
|
len(r.data) < maxrecordsize):
|
|
raise SwError(SW["ERR_INCOMPATIBLEWITHFILE"])
|
|
self.setdec('records', records)
|
|
self.resetRecordPointer()
|
|
self.maxrecordsize=maxrecordsize
|
|
|
|
def resetRecordPointer(self):
|
|
"""Resets the record pointer."""
|
|
self.recordpointer=-1
|
|
|
|
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"]):
|
|
return True
|
|
else:
|
|
return False
|
|
|
|
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"]):
|
|
return True
|
|
else: return False
|
|
|
|
def hasFixedRecordSize(self):
|
|
"""Returns True if the records are of fixed size, False otherwise."""
|
|
if (self.filedescriptor == FDB["EFSTRUCTURE_LINEAR_FIXED_SIMPLETLV"] or
|
|
self.filedescriptor ==
|
|
FDB["EFSTRUCTURE_LINEAR_FIXED_NOFURTHERINFO"]):
|
|
return True
|
|
else:
|
|
return False
|
|
|
|
def __getRecords(self, num_id, reference):
|
|
"""
|
|
Returns a list of records.
|
|
|
|
num_id -- The requested record's number or identifier
|
|
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)
|
|
else:
|
|
return self.__getRecordsByIdentifier(num_id, reference)
|
|
|
|
def __getRecordsByNumber(self, number, reference):
|
|
"""
|
|
Returns a list of records. Is to be involved by __getRecords.
|
|
|
|
number -- The requested record's number
|
|
reference -- Specifies which record to select (usually the last 3 bits of 'p1' of a record handling command)
|
|
"""
|
|
result = []
|
|
records = self.getenc('records')
|
|
|
|
if number == 0:
|
|
# refer to the current record
|
|
start = self.recordpointer
|
|
else:
|
|
start = number - 1
|
|
|
|
if reference == REF["NUMBER"]:
|
|
end = start + 1
|
|
else:
|
|
# Read all records from number up to the last OR
|
|
# Read all records from the last up to number
|
|
end = len(records)
|
|
|
|
result = records[start:end]
|
|
if reference == REF["NUMBER_FROM_LAST"]:
|
|
result.reverse()
|
|
|
|
if result == []:
|
|
raise SwError(SW["ERR_RECORDNOTFOUND"])
|
|
|
|
return result
|
|
|
|
def __getRecordsByIdentifier(self, id, reference):
|
|
"""
|
|
Returns a list of records and sets the recordpointer to the first
|
|
record, that matched. Is to be involved by __getRecords.
|
|
|
|
id -- The requested record's identifier
|
|
reference -- Specifies which record to select (usually the last 3 bits of 'p1' of a record handling command)
|
|
"""
|
|
result = []
|
|
records = self.getenc('records')
|
|
|
|
indexes = get_indexes(records, reference, self.recordpointer)
|
|
for i in indexes:
|
|
if (not self.hasSimpleTlv()) or records[i].identifier==id:
|
|
if result == []:
|
|
self.recordpointer=i
|
|
result.append(records[i])
|
|
|
|
if result == []:
|
|
self.resetRecordPointer()
|
|
raise SwError(SW["ERR_RECORDNOTFOUND"])
|
|
|
|
return result
|
|
|
|
def readrecord(self, offset, num_id, reference):
|
|
"""
|
|
Returns a data string from the given 'offset'. 'num_id' and 'reference'
|
|
specify the record (see __getRecords).
|
|
"""
|
|
records = self.__getRecords(num_id, reference)
|
|
result = []
|
|
for r in records:
|
|
result.append(r.data[offset:])
|
|
if offset == 0:
|
|
pass
|
|
elif offset > 0 and offset > len(r.data):
|
|
offset -= len(r.data)
|
|
else:
|
|
offset = 0
|
|
|
|
return result
|
|
|
|
def writerecord(self, num_id, reference, offset, data,
|
|
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
|
|
__getRecords).
|
|
"""
|
|
if self.isCyclic() and reference == REF["IDENTIFIER_PREVIOUS"]:
|
|
return self.appendrecord(data)
|
|
|
|
records = self.__getRecords(num_id, reference)
|
|
|
|
if datacoding:
|
|
records[0].data = write(records[0].data, [data], [0], datacoding,
|
|
self.maxrecordsize)
|
|
else:
|
|
records[0].data = write(records[0].data, [data], [0],
|
|
self.datacoding, self.maxrecordsize)
|
|
|
|
if self.hasSimpleTlv():
|
|
# identifier/tag could have changed
|
|
records[0].identifier = simpletlv_unpack(records[0].data)[0][0]
|
|
|
|
def updaterecord(self, num_id, reference, offset, data):
|
|
"""
|
|
x.updaterecord(num_id, reference, offset, data) <==> x.writerecord(num_id, reference, offset, data, DCB["ONETIMEWRITE"])
|
|
"""
|
|
return writerecord(num_id, reference, offset, data, DCB["ONETIMEWRITE"])
|
|
|
|
def appendrecord(self, data):
|
|
"""
|
|
Appends a new record to the file, initializing it with the given data
|
|
string. Sets the recordpointer to the newly created record.
|
|
"""
|
|
if self.hasSimpleTlv():
|
|
recordidentifier = simpletlv_unpack(data)[0][0]
|
|
else:
|
|
recordidentifier = None
|
|
|
|
l = len(data)
|
|
if l > self.maxrecordsize:
|
|
raise SwError(SW["ERR_INCORRECTPARAMETERS"])
|
|
|
|
if self.hasFixedRecordSize():
|
|
data = chr(0)*(self.maxrecordsize) + data
|
|
|
|
records = self.getenc('records')
|
|
if self.isCyclic():
|
|
records.insert(0, Record(recordidentifier, data))
|
|
self.recordpointer=0
|
|
else:
|
|
records.append(Record(recordidentifier, data))
|
|
self.recordpointer=len(records)-1
|
|
self.setdec('records', records)
|
|
|
|
def eraserecord(self, num_id, reference):
|
|
"""
|
|
Removes a record from the file. 'num_id' and 'reference' specify the
|
|
record (see __getRecords).
|
|
"""
|
|
records = self.__getRecords(num_id, reference)
|
|
for r in records:
|
|
r.data = ""
|
|
r.identifier = None
|
|
return SW["NORMAL"]
|
|
# }}}
|
|
|
|
|
|
|
|
class CryptoflexMF(MF): # {{{
|
|
|
|
@staticmethod
|
|
def create(p1, p2, data):
|
|
|
|
if data[0:2] != "\xff\xff":
|
|
raise SwError(SW["ERR_INCORRECTPARAMETERS"])
|
|
|
|
args = {
|
|
"parent": None,
|
|
"filedescriptor": 0,
|
|
"fid": stringtoint(data[4:6]),
|
|
}
|
|
if data[6] == "\x01":
|
|
args["data"] = chr(0)*stringtoint(data[2:4])
|
|
args["filedescriptor"] = FDB["EFSTRUCTURE_TRANSPARENT"]
|
|
file = TransparentStructureEF(**args)
|
|
elif data[6] == "\x02":
|
|
if len(data)>16:
|
|
args["maxrecordsize"] = stringtoint(data[16])
|
|
elif p2:
|
|
# if given a number of records
|
|
args["maxrecordsize"] = (stringtoint(data[2:4])/p2)
|
|
args["filedescriptor"] = FDB["EFSTRUCTURE_LINEAR_FIXED_NOFURTHERINFO"]
|
|
file = RecordStructureEF(**args)
|
|
elif data[6] == "\x03":
|
|
args["filedescriptor"] = FDB["EFSTRUCTURE_LINEAR_VARIABLE_NOFURTHERINFO"]
|
|
file = RecordStructureEF(**args)
|
|
elif data[6] == "\x04":
|
|
args["filedescriptor"] = FDB["EFSTRUCTURE_CYCLIC_NOFURTHERINFO"]
|
|
file = RecordStructureEF(**args)
|
|
elif data[6] == "\x38":
|
|
if data[12] != "\x03":
|
|
raise SwError(SW["ERR_INCORRECTPARAMETERS"])
|
|
file = DF(**args)
|
|
else:
|
|
print "unknown type: 0x%x" % ord(data[6])
|
|
raise SwError(SW["ERR_INCORRECTPARAMETERS"])
|
|
|
|
|
|
return [file]
|
|
|
|
def recordHandlingDecode(self, p1, p2):
|
|
if p2 < 4:
|
|
p1 = 0
|
|
|
|
return MF.recordHandlingDecode(self, p1, p2)
|
|
|
|
|
|
def dataUnitsDecodePlain(self, p1, p2, data):
|
|
ef = self.currentEF()
|
|
# FIXME: Cyberflex Access TM Software Development Kit Release 3C
|
|
# enforces the following:
|
|
# offsets = [(p2 << 8) + p1]
|
|
# but smartcard_login-0.1.1 requires:
|
|
offsets = [(p1 << 8) + p2]
|
|
|
|
datalist = [data]
|
|
return ef, offsets, datalist
|
|
|
|
def selectFile(self, p1, p2, data):
|
|
"""
|
|
Function for instruction 0xa4. 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.
|
|
"""
|
|
P2_FCI = 0
|
|
P2_FCP = 1<<2
|
|
P2_FMD = 2<<2
|
|
P2_NONE = 3<<2
|
|
file = self._selectFile(p1, p2, data)
|
|
|
|
if isinstance(file, EF):
|
|
size = inttostring(min(0xffff, len(file.getenc('data'))), 2) # File size (body only)
|
|
extra = chr(0) # RFU
|
|
if isinstance(file, RecordStructureEF) and file.hasFixedRecordSize() and not file.isCyclic():
|
|
extra += chr(0) + chr(min(file.records, 0xff)) # Length of records
|
|
elif isinstance(file, DF):
|
|
size = inttostring(0xffff, 2) # Number of unused EEPROM bytes available in the DF
|
|
efcount = 0
|
|
dfcount = 0
|
|
chv1 = None
|
|
chv2 = None
|
|
chv1lifecycle = 0
|
|
chv2lifecycle = 0
|
|
for f in self.content:
|
|
if f.fid == 0x0000:
|
|
chv1 = f
|
|
chv1lifecycle = f.lifecycle & 1
|
|
if f.fid == 0x0100:
|
|
chv2 = f
|
|
chv2lifecycle = f.lifecycle & 1
|
|
if isinstance(f, EF):
|
|
efcount += 1
|
|
if isinstance(f, DF):
|
|
dfcount += 1
|
|
if chv1:
|
|
extra = chr(1) # TODO LSB correct?
|
|
else:
|
|
extra = chr(0) # TODO LSB correct?
|
|
extra += chr(efcount)
|
|
extra += chr(dfcount)
|
|
extra += chr(0) # TODO Number of PINs and unblock CHV PINs
|
|
extra += chr(0) # RFU
|
|
if chv1:
|
|
extra += chr(0) # TODO Number of remaining CHV1 attempts
|
|
extra += chr(0) # TODO Number of remaining unblock CHV1 attempts
|
|
if chv2:
|
|
extra += chr(0) # TODO CHV2 key status
|
|
extra += chr(0) # TODO CHV2 unblocking key status
|
|
|
|
data = inttostring(0, 2) # RFU
|
|
data += size
|
|
data += inttostring(file.fid, 2)
|
|
data += inttostring(file.filedescriptor) # File type
|
|
data += inttostring(0, 4) # ACs TODO
|
|
data += chr(file.lifecycle & 1) # File status
|
|
data += chr(len(extra))
|
|
data += extra
|
|
|
|
self.current = file
|
|
|
|
return SW["NORMAL"], data
|
|
# }}}
|