From cde9393e8927620b6c2a641ed9d7f09e294013f7 Mon Sep 17 00:00:00 2001 From: Frank Morgner Date: Tue, 19 May 2015 08:09:05 +0200 Subject: [PATCH] moved CryptoflexOS to cryptoflex.py --- .../virtualsmartcard/VirtualSmartcard.py | 48 +- .../vpicc/virtualsmartcard/cards/Iso7816.py | 1966 +++++++++++++++++ .../virtualsmartcard/cards/cryptoflex.py | 52 +- 3 files changed, 2017 insertions(+), 49 deletions(-) create mode 100644 virtualsmartcard/src/vpicc/virtualsmartcard/cards/Iso7816.py diff --git a/virtualsmartcard/src/vpicc/virtualsmartcard/VirtualSmartcard.py b/virtualsmartcard/src/vpicc/virtualsmartcard/VirtualSmartcard.py index d325d5f..d0c9f82 100644 --- a/virtualsmartcard/src/vpicc/virtualsmartcard/VirtualSmartcard.py +++ b/virtualsmartcard/src/vpicc/virtualsmartcard/VirtualSmartcard.py @@ -350,53 +350,6 @@ class Iso7816OS(SmartcardOS): return answer -class CryptoflexOS(Iso7816OS): - def __init__(self, mf, sam, ins2handler=None, maxle=MAX_SHORT_LE): - Iso7816OS.__init__(self, mf, sam, ins2handler, maxle) - self.atr = '\x3B\xE2\x00\x00\x40\x20\x49\x06' - - def execute(self, msg): - def notImplemented(*argz, **args): - raise SwError(SW["ERR_INSNOTSUPPORTED"]) - - try: - c = C_APDU(msg) - except ValueError as e: - logging.debug("Failed to parse APDU %s", msg) - return self.formatResult(False, 0, 0, "", SW["ERR_INCORRECTPARAMETERS"]) - - try: - sw, result = self.ins2handler.get(c.ins, notImplemented)(c.p1, c.p2, c.data) - #print type(result) - except SwError as e: - logging.info(e.message) - #traceback.print_exception(*sys.exc_info()) - sw = e.sw - result = "" - - r = self.formatResult(c.ins, c.le, result, sw) - return r - - def formatResult(self, ins, le, data, sw): - if le == 0 and len(data): - # cryptoflex does not inpterpret le==0 as maxle - self.lastCommandSW = sw - self.lastCommandOffcut = data - r = R_APDU(inttostring(SW["ERR_WRONGLENGTH"] +\ - min(0xff, len(data)))).render() - else: - if ins == 0xa4 and len(data): - # get response should be followed by select file - self.lastCommandSW = sw - self.lastCommandOffcut = data - r = R_APDU(inttostring(SW["NORMAL_REST"] +\ - min(0xff, len(data)))).render() - else: - r = Iso7816OS.formatResult(self, Iso7816OS.seekable(ins), le, data, sw, False) - - return r - - class NPAOS(Iso7816OS): def __init__(self, mf, sam, ins2handler=None, maxle=MAX_EXTENDED_LE, ef_cardsecurity=None, ef_cardaccess=None, ca_key=None, cvca=None, disable_checks=False, esign_key=None, esign_ca_cert=None, esign_cert=None): Iso7816OS.__init__(self, mf, sam, ins2handler, maxle) @@ -578,6 +531,7 @@ class VirtualICC(object): elif card_type == "nPA": 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) elif card_type == "relay": from virtualsmartcard.cards.Relay import RelayOS diff --git a/virtualsmartcard/src/vpicc/virtualsmartcard/cards/Iso7816.py b/virtualsmartcard/src/vpicc/virtualsmartcard/cards/Iso7816.py new file mode 100644 index 0000000..62cdcf8 --- /dev/null +++ b/virtualsmartcard/src/vpicc/virtualsmartcard/cards/Iso7816.py @@ -0,0 +1,1966 @@ +# +# Copyright (C) 2011-2015 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 . +# + +from virtualsmartcard.VirtualSmartcard import SmartcardOS + + +class Iso7816OS(SmartcardOS): + 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, + 0x0e: self.mf.eraseBinaryPlain, + 0x0f: self.mf.eraseBinaryEncapsulated, + 0x2a: self.SAM.perform_security_operation, + 0x20: self.SAM.verify, + 0x22: self.SAM.manage_security_environment, + 0x24: self.SAM.change_reference_data, + 0x46: self.SAM.generate_public_key_pair, + 0x82: self.SAM.external_authenticate, + 0x84: self.SAM.get_challenge, + 0x88: self.SAM.internal_authenticate, + 0xa0: self.mf.searchBinaryPlain, + 0xa1: self.mf.searchBinaryEncapsulated, + 0xa4: self.mf.selectFile, + 0xb0: self.mf.readBinaryPlain, + 0xb1: self.mf.readBinaryEncapsulated, + 0xb2: self.mf.readRecordPlain, + 0xb3: self.mf.readRecordEncapsulated, + 0xc0: self.getResponse, + 0xca: self.mf.getDataPlain, + 0xcb: self.mf.getDataEncapsulated, + 0xd0: self.mf.writeBinaryPlain, + 0xd1: self.mf.writeBinaryEncapsulated, + 0xd2: self.mf.writeRecord, + 0xd6: self.mf.updateBinaryPlain, + 0xd7: self.mf.updateBinaryEncapsulated, + 0xda: self.mf.putDataPlain, + 0xdb: self.mf.putDataEncapsulated, + 0xdc: self.mf.updateRecordPlain, + 0xdd: self.mf.updateRecordEncapsulated, + 0xe0: self.mf.createFile, + 0xe2: self.mf.appendRecord, + 0xe4: self.mf.deleteFile, + } + else: + self.ins2handler = ins2handler + + if extended_length: + self.maxle = MAX_EXTENDED_LE + else: + self.maxle = MAX_SHORT_LE + + 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) + + def getATR(self): + return self.atr + + @staticmethod + def makeATR(**args): + """Calculate Answer to Reset (ATR) and returns the bitstring. + + - 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 + 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 + omitted. + - histChars (optional): Bitstring with 0 <= len(histChars) <= 15. + Historical Characters T1 to T15 (for meaning + see ISO 7816-4). + + T0, TDi and TCK are automatically calculated. + """ + # first byte TS + if args["directConvention"]: + atr = "\x3b" + else: + atr = "\x3f" + + if args.has_key("T"): + T = args["T"] + else: + T = 0 + + # find maximum i of TAi/TBi/TCi in args + 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)): + 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"): + args["TD0"] = len(args["histChars"]) + else: + args["TD"+str(i)] = T + + if i < maxTD: + args["TD"+str(i)] |= 1<<7 + + 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)): + 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"): + atr += args["histChars"] + for i in range(0, len(args["histChars"])): + 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): + """ + Returns a byte according to the third software function table from the + historical bytes of the card capabilities. + """ + tsft = 0 + if commandChainging: + tsft |= 1 << 7 + if extendedLe: + tsft |= 1 << 6 + if assignLogicalChannel: + if not (0<=assignLogicalChannel and assignLogicalChannel<=3): + raise ValueError + tsft |= assignLogicalChannel << 3 + if maximumChannels: + 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: + self.lastCommandOffcut = data[le:] + l = len(self.lastCommandOffcut) + if l == 0: + self.lastCommandSW = SW["NORMAL"] + else: + self.lastCommandSW = sw + sw = SW["NORMAL_REST"] + min(0xff, l) + else: + if le > len(data): + sw = SW["WARN_EOFBEFORENEREAD"] + + if le != None: + result = data[:le] + else: + result = data[:0] + if sm: + sw, result = self.SAM.protect_result(sw, result) + + return R_APDU(result, inttostring(sw)).render() + + @staticmethod + def seekable(ins): + if ins in [0xb0, 0xb1, 0xd0, 0xd1, 0xd6, 0xd7, 0xa0, 0xa1, 0xb2, 0xb3, 0xdc, 0xdd ]: + return True + else: + return False + + def getResponse(self, p1, p2, data): + if not (p1 == 0 and p2 == 0): + raise SwError(SW["ERR_INCORRECTP1P2"]) + + return self.lastCommandSW, self.lastCommandOffcut + + 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 + should result in an appropriate response APDU being passed to the + application. + """ + raise SwError(SW["ERR_INSNOTSUPPORTED"]) + + try: + c = C_APDU(msg) + except ValueError as e: + logging.warning(str(e)) + return self.formatResult(False, 0, "", SW["ERR_INCORRECTPARAMETERS"], False) + + logging.info("Parsed APDU:\n%s", str(c)) + + #Handle Class Byte + #{{{ + class_byte = c.cla + SM_STATUS = None + logical_channel = 0 + command_chaining = 0 + header_authentication = 0 + + #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 (class_byte & 0xE0 == 0x00): + #Bit 1 and 2 specify the logical channel + logical_channel = class_byte & 0x03 + #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 ? + elif (secure_messaging == 0x02): + 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 + elif (class_byte & 0x0C == 0x0C): + #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 + secure_messaging = class_byte >> 6 + if (secure_messaging == 0x00): + 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 + command_chaining = class_byte >> 5 + command_chaining &= 0x01 + #}}} + + sm = False + 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) + except SwError as e: + logging.info(e.message) + import traceback + traceback.print_exception(*sys.exc_info()) + sw = e.sw + result = "" + answer = self.formatResult(False, 0, result, sw, sm) + + return answer + + +#TODO: use bertlv_pack for fdm +#TODO: zu lange daten abschneiden und trotzdem tlv laenge beibehalten + +from pickle import dumps, loads +import logging +from virtualsmartcard.ConstantDefinitions import DCB, FDB, FID, LCB, REF +from virtualsmartcard.TLVutils import * +from virtualsmartcard.SWutils import SW, SwError +from virtualsmartcard.utils import stringtoint, inttostring, hexdump + +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). + + :param start: DF, where to look for the first fid + :param path: a string of fids + """ + if len(path) % 2 != 0: + raise SwError(SW["ERR_INCORRECTPARAMETERS"]) + index = 0 + while index + 2 <= len(path): + if not isinstance(start, DF): + # File or application not found + raise SwError(SW["ERR_FILENOTFOUND"]) + start = start.select('fid', stringtoint(path[index:index+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. + + param mf: the MF + param 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 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 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 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"]): + # 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) + s = s + "\n" + indent + hexdump(newvalue, len(indent)) + 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, + 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): + 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 + 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") + 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") + self.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 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 = getattr(self, 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 = getattr(self, 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) + setattr(self, attribute, tlv_data) + + def readbinary(self, *argz, **args): + """Only a template, will raise an error.""" + raise SwError(SW["ERR_INCOMPATIBLEWITHFILE"]) + + def writebinary(self, *argz, **args): + """Only a template, will raise an error.""" + raise SwError(SW["ERR_INCOMPATIBLEWITHFILE"]) + + def updatebinary(self, *argz, **args): + """Only a template, will raise an error.""" + raise SwError(SW["ERR_INCOMPATIBLEWITHFILE"]) + + def erasebinary(self, *argz, **args): + """Only a template, will raise an error.""" + raise SwError(SW["ERR_INCOMPATIBLEWITHFILE"]) + + def readrecord(self, *argz, **args): + """Only a template, will raise an error.""" + raise SwError(SW["ERR_INCOMPATIBLEWITHFILE"]) + + def writerecord(self, *argz, **args): + """Only a template, will raise an error.""" + raise SwError(SW["ERR_INCOMPATIBLEWITHFILE"]) + + def appendrecord(self, *argz, **args): + """Only a template, will raise an error.""" + raise SwError(SW["ERR_INCOMPATIBLEWITHFILE"]) + + def updaterecord(self, *argz, **args): + """Only a template, will raise an error.""" + raise SwError(SW["ERR_INCOMPATIBLEWITHFILE"]) + + def select(self, *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.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 self.content[key] + + def __contains__(self, item): + """ + x.__contains__(item) <==> item in x.content + """ + return item in self.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): + 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)) + raise SwError(SW["ERR_FILENOTFOUND"]) + + def remove(self, file): + """Removes 'file' from the content of the DF""" + self.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. + """ + 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) ] + fdm.append(file.extra_fci_data) + + # 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.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.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.currentDF().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_SELECT"], index_current) + else: + logging.debug("unknown selection method: p1 =%s" % 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 *even* 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 *odd* 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_RECORD"] + + 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_RECORD"]) != 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): + 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)', + } + 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: + logging.debug('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.data = data + + def readbinary(self, offset): + """Returns the string of decrypted data beginning at 'offset'.""" + data = self.data + + if offset == 0: + return data + else: + if offset + 1 > 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. + + :param offsets: list of integers. Offsets. + :param datalist: list of strings. Data pieces. + :param datacoding: the data coding byte to use for writing + """ + data = self.data + if datacoding: + data = write(data, datalist, offsets, datacoding) + else: + data = write(data, datalist, offsets, self.datacoding) + self.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.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.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. + + :param records: list of Records + :para, 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.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. + + :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) + """ + 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. + + :param number: The requested record's number + :param reference: Specifies which record to select (usually the last 3 + bits of 'p1' of a record handling command) + """ + result = [] + records = self.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. + + :param id: The requested record's identifier + :param reference: Specifies which record to select (usually the last 3 + bits of 'p1' of a record handling command) + """ + 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 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: + if offset == 0: + result.append(r.data) + else: + result.append(r.data[offset:]) + if 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 self.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.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.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"] diff --git a/virtualsmartcard/src/vpicc/virtualsmartcard/cards/cryptoflex.py b/virtualsmartcard/src/vpicc/virtualsmartcard/cards/cryptoflex.py index 154160b..4f50b1d 100644 --- a/virtualsmartcard/src/vpicc/virtualsmartcard/cards/cryptoflex.py +++ b/virtualsmartcard/src/vpicc/virtualsmartcard/cards/cryptoflex.py @@ -19,15 +19,63 @@ from virtualsmartcard.SmartcardSAM import SAM from virtualsmartcard.SEutils import Security_Environment from virtualsmartcard.SWutils import SwError, SW -from virtualsmartcard.utils import inttostring, stringtoint +from virtualsmartcard.utils import inttostring, stringtoint, C_APDU, R_APDU from virtualsmartcard import TLVutils +from virtualsmartcard.VirtualSmartcard import Iso7816OS from virtualsmartcard.SmartcardFilesystem import MF, TransparentStructureEF, \ RecordStructureEF, DF, EF -from virtualsmartcard.ConstantDefinitions import FDB +from virtualsmartcard.ConstantDefinitions import FDB, MAX_SHORT_LE import struct, logging + +class CryptoflexOS(Iso7816OS): + def __init__(self, mf, sam, ins2handler=None, maxle=MAX_SHORT_LE): + Iso7816OS.__init__(self, mf, sam, ins2handler, maxle) + self.atr = '\x3B\xE2\x00\x00\x40\x20\x49\x06' + + def execute(self, msg): + def notImplemented(*argz, **args): + raise SwError(SW["ERR_INSNOTSUPPORTED"]) + + try: + c = C_APDU(msg) + except ValueError as e: + logging.debug("Failed to parse APDU %s", msg) + return self.formatResult(False, 0, 0, "", SW["ERR_INCORRECTPARAMETERS"]) + + try: + sw, result = self.ins2handler.get(c.ins, notImplemented)(c.p1, c.p2, c.data) + #print type(result) + except SwError as e: + logging.info(e.message) + #traceback.print_exception(*sys.exc_info()) + sw = e.sw + result = "" + + r = self.formatResult(c.ins, c.le, result, sw) + return r + + def formatResult(self, ins, le, data, sw): + if le == 0 and len(data): + # cryptoflex does not inpterpret le==0 as maxle + self.lastCommandSW = sw + self.lastCommandOffcut = data + r = R_APDU(inttostring(SW["ERR_WRONGLENGTH"] +\ + min(0xff, len(data)))).render() + else: + if ins == 0xa4 and len(data): + # get response should be followed by select file + self.lastCommandSW = sw + self.lastCommandOffcut = data + r = R_APDU(inttostring(SW["NORMAL_REST"] +\ + min(0xff, len(data)))).render() + else: + r = Iso7816OS.formatResult(self, Iso7816OS.seekable(ins), le, data, sw, False) + + return r + class CryptoflexSE(Security_Environment): def __init__(self, MF, SAM): Security_Environment.__init__(self, MF, SAM)