cosmetic: Switch to consistent four-spaces indent; run autopep8

We had a mixture of tab and 4space based indenting, which is a bad
idea.  4space is the standard in python, so convert all our code to
that.  The result unfortuantely still shoed even more inconsistencies,
so I've decided to run autopep8 on the entire code base.

Change-Id: I4a4b1b444a2f43fab05fc5d2c8a7dd6ddecb5f07
This commit is contained in:
Harald Welte 2022-02-10 18:05:45 +01:00
parent 181c7c5930
commit c91085e744
29 changed files with 7501 additions and 6549 deletions

File diff suppressed because it is too large Load Diff

View File

@ -41,312 +41,322 @@ from pySim.utils import format_xplmn_w_act, dec_st
from pySim.utils import h2s, format_ePDGSelection
option_parser = argparse.ArgumentParser(prog='pySim-read',
description='Legacy tool for reading some parts of a SIM card',
formatter_class=argparse.ArgumentDefaultsHelpFormatter)
description='Legacy tool for reading some parts of a SIM card',
formatter_class=argparse.ArgumentDefaultsHelpFormatter)
argparse_add_reader_args(option_parser)
def select_app(adf:str, card:SimCard):
"""Select application by its AID"""
sw = 0
try:
if card._scc.cla_byte == "00":
data, sw = card.select_adf_by_aid(adf)
except SwMatchError as e:
if e.sw_actual == "6a82":
# If we can't select the file because it does not exist, we just remain silent since it means
# that this card just does not have an USIM application installed, which is not an error.
pass
else:
print("ADF." + adf + ": Can't select application -- " + str(e))
except Exception as e:
print("ADF." + adf + ": Can't select application -- " + str(e))
return sw
def select_app(adf: str, card: SimCard):
"""Select application by its AID"""
sw = 0
try:
if card._scc.cla_byte == "00":
data, sw = card.select_adf_by_aid(adf)
except SwMatchError as e:
if e.sw_actual == "6a82":
# If we can't select the file because it does not exist, we just remain silent since it means
# that this card just does not have an USIM application installed, which is not an error.
pass
else:
print("ADF." + adf + ": Can't select application -- " + str(e))
except Exception as e:
print("ADF." + adf + ": Can't select application -- " + str(e))
return sw
if __name__ == '__main__':
# Parse options
opts = option_parser.parse_args()
# Parse options
opts = option_parser.parse_args()
# Init card reader driver
sl = init_reader(opts)
if sl is None:
exit(1)
# Init card reader driver
sl = init_reader(opts)
if sl is None:
exit(1)
# Create command layer
scc = SimCardCommands(transport=sl)
# Create command layer
scc = SimCardCommands(transport=sl)
# Wait for SIM card
sl.wait_for_card()
# Wait for SIM card
sl.wait_for_card()
# Assuming UICC SIM
scc.cla_byte = "00"
scc.sel_ctrl = "0004"
# Assuming UICC SIM
scc.cla_byte = "00"
scc.sel_ctrl = "0004"
# Testing for Classic SIM or UICC
(res, sw) = sl.send_apdu(scc.cla_byte + "a4" + scc.sel_ctrl + "02" + "3f00")
if sw == '6e00':
# Just a Classic SIM
scc.cla_byte = "a0"
scc.sel_ctrl = "0000"
# Testing for Classic SIM or UICC
(res, sw) = sl.send_apdu(scc.cla_byte + "a4" + scc.sel_ctrl + "02" + "3f00")
if sw == '6e00':
# Just a Classic SIM
scc.cla_byte = "a0"
scc.sel_ctrl = "0000"
# Read the card
print("Reading ...")
# Read the card
print("Reading ...")
# Initialize Card object by auto detecting the card
card = card_detect("auto", scc) or SimCard(scc)
# Initialize Card object by auto detecting the card
card = card_detect("auto", scc) or SimCard(scc)
# Read all AIDs on the UICC
card.read_aids()
# Read all AIDs on the UICC
card.read_aids()
# EF.ICCID
(res, sw) = card.read_iccid()
if sw == '9000':
print("ICCID: %s" % (res,))
else:
print("ICCID: Can't read, response code = %s" % (sw,))
# EF.ICCID
(res, sw) = card.read_iccid()
if sw == '9000':
print("ICCID: %s" % (res,))
else:
print("ICCID: Can't read, response code = %s" % (sw,))
# EF.IMSI
(res, sw) = card.read_imsi()
if sw == '9000':
print("IMSI: %s" % (res,))
else:
print("IMSI: Can't read, response code = %s" % (sw,))
# EF.IMSI
(res, sw) = card.read_imsi()
if sw == '9000':
print("IMSI: %s" % (res,))
else:
print("IMSI: Can't read, response code = %s" % (sw,))
# EF.GID1
try:
(res, sw) = card.read_gid1()
if sw == '9000':
print("GID1: %s" % (res,))
else:
print("GID1: Can't read, response code = %s" % (sw,))
except Exception as e:
print("GID1: Can't read file -- %s" % (str(e),))
# EF.GID1
try:
(res, sw) = card.read_gid1()
if sw == '9000':
print("GID1: %s" % (res,))
else:
print("GID1: Can't read, response code = %s" % (sw,))
except Exception as e:
print("GID1: Can't read file -- %s" % (str(e),))
# EF.GID2
try:
(res, sw) = card.read_binary('GID2')
if sw == '9000':
print("GID2: %s" % (res,))
else:
print("GID2: Can't read, response code = %s" % (sw,))
except Exception as e:
print("GID2: Can't read file -- %s" % (str(e),))
# EF.GID2
try:
(res, sw) = card.read_binary('GID2')
if sw == '9000':
print("GID2: %s" % (res,))
else:
print("GID2: Can't read, response code = %s" % (sw,))
except Exception as e:
print("GID2: Can't read file -- %s" % (str(e),))
# EF.SMSP
(res, sw) = card.read_record('SMSP', 1)
if sw == '9000':
print("SMSP: %s" % (res,))
else:
print("SMSP: Can't read, response code = %s" % (sw,))
# EF.SMSP
(res, sw) = card.read_record('SMSP', 1)
if sw == '9000':
print("SMSP: %s" % (res,))
else:
print("SMSP: Can't read, response code = %s" % (sw,))
# EF.SPN
try:
(res, sw) = card.read_spn()
if sw == '9000':
print("SPN: %s" % (res[0] or "Not available"))
print("Show in HPLMN: %s" % (res[1],))
print("Hide in OPLMN: %s" % (res[2],))
else:
print("SPN: Can't read, response code = %s" % (sw,))
except Exception as e:
print("SPN: Can't read file -- %s" % (str(e),))
# EF.SPN
try:
(res, sw) = card.read_spn()
if sw == '9000':
print("SPN: %s" % (res[0] or "Not available"))
print("Show in HPLMN: %s" % (res[1],))
print("Hide in OPLMN: %s" % (res[2],))
else:
print("SPN: Can't read, response code = %s" % (sw,))
except Exception as e:
print("SPN: Can't read file -- %s" % (str(e),))
# EF.PLMNsel
try:
(res, sw) = card.read_binary('PLMNsel')
if sw == '9000':
print("PLMNsel: %s" % (res))
else:
print("PLMNsel: Can't read, response code = %s" % (sw,))
except Exception as e:
print("PLMNsel: Can't read file -- " + str(e))
# EF.PLMNsel
try:
(res, sw) = card.read_binary('PLMNsel')
if sw == '9000':
print("PLMNsel: %s" % (res))
else:
print("PLMNsel: Can't read, response code = %s" % (sw,))
except Exception as e:
print("PLMNsel: Can't read file -- " + str(e))
# EF.PLMNwAcT
try:
(res, sw) = card.read_plmn_act()
if sw == '9000':
print("PLMNwAcT:\n%s" % (res))
else:
print("PLMNwAcT: Can't read, response code = %s" % (sw,))
except Exception as e:
print("PLMNwAcT: Can't read file -- " + str(e))
# EF.PLMNwAcT
try:
(res, sw) = card.read_plmn_act()
if sw == '9000':
print("PLMNwAcT:\n%s" % (res))
else:
print("PLMNwAcT: Can't read, response code = %s" % (sw,))
except Exception as e:
print("PLMNwAcT: Can't read file -- " + str(e))
# EF.OPLMNwAcT
try:
(res, sw) = card.read_oplmn_act()
if sw == '9000':
print("OPLMNwAcT:\n%s" % (res))
else:
print("OPLMNwAcT: Can't read, response code = %s" % (sw,))
except Exception as e:
print("OPLMNwAcT: Can't read file -- " + str(e))
# EF.OPLMNwAcT
try:
(res, sw) = card.read_oplmn_act()
if sw == '9000':
print("OPLMNwAcT:\n%s" % (res))
else:
print("OPLMNwAcT: Can't read, response code = %s" % (sw,))
except Exception as e:
print("OPLMNwAcT: Can't read file -- " + str(e))
# EF.HPLMNAcT
try:
(res, sw) = card.read_hplmn_act()
if sw == '9000':
print("HPLMNAcT:\n%s" % (res))
else:
print("HPLMNAcT: Can't read, response code = %s" % (sw,))
except Exception as e:
print("HPLMNAcT: Can't read file -- " + str(e))
# EF.HPLMNAcT
try:
(res, sw) = card.read_hplmn_act()
if sw == '9000':
print("HPLMNAcT:\n%s" % (res))
else:
print("HPLMNAcT: Can't read, response code = %s" % (sw,))
except Exception as e:
print("HPLMNAcT: Can't read file -- " + str(e))
# EF.ACC
(res, sw) = card.read_binary('ACC')
if sw == '9000':
print("ACC: %s" % (res,))
else:
print("ACC: Can't read, response code = %s" % (sw,))
# EF.ACC
(res, sw) = card.read_binary('ACC')
if sw == '9000':
print("ACC: %s" % (res,))
else:
print("ACC: Can't read, response code = %s" % (sw,))
# EF.MSISDN
try:
(res, sw) = card.read_msisdn()
if sw == '9000':
# (npi, ton, msisdn) = res
if res is not None:
print("MSISDN (NPI=%d ToN=%d): %s" % res)
else:
print("MSISDN: Not available")
else:
print("MSISDN: Can't read, response code = %s" % (sw,))
except Exception as e:
print("MSISDN: Can't read file -- " + str(e))
# EF.MSISDN
try:
(res, sw) = card.read_msisdn()
if sw == '9000':
# (npi, ton, msisdn) = res
if res is not None:
print("MSISDN (NPI=%d ToN=%d): %s" % res)
else:
print("MSISDN: Not available")
else:
print("MSISDN: Can't read, response code = %s" % (sw,))
except Exception as e:
print("MSISDN: Can't read file -- " + str(e))
# EF.AD
(res, sw) = card.read_binary('AD')
if sw == '9000':
print("Administrative data: %s" % (res,))
ad = EF_AD()
decoded_data = ad.decode_hex(res)
print("\tMS operation mode: %s" % decoded_data['ms_operation_mode'])
if decoded_data['ofm']:
print("\tCiphering Indicator: enabled")
else:
print("\tCiphering Indicator: disabled")
else:
print("AD: Can't read, response code = %s" % (sw,))
# EF.AD
(res, sw) = card.read_binary('AD')
if sw == '9000':
print("Administrative data: %s" % (res,))
ad = EF_AD()
decoded_data = ad.decode_hex(res)
print("\tMS operation mode: %s" % decoded_data['ms_operation_mode'])
if decoded_data['ofm']:
print("\tCiphering Indicator: enabled")
else:
print("\tCiphering Indicator: disabled")
else:
print("AD: Can't read, response code = %s" % (sw,))
# EF.SST
(res, sw) = card.read_binary('SST')
if sw == '9000':
print("SIM Service Table: %s" % res)
# Print those which are available
print("%s" % dec_st(res))
else:
print("SIM Service Table: Can't read, response code = %s" % (sw,))
# EF.SST
(res, sw) = card.read_binary('SST')
if sw == '9000':
print("SIM Service Table: %s" % res)
# Print those which are available
print("%s" % dec_st(res))
else:
print("SIM Service Table: Can't read, response code = %s" % (sw,))
# Check whether we have th AID of USIM, if so select it by its AID
# EF.UST - File Id in ADF USIM : 6f38
sw = select_app("USIM", card)
if sw == '9000':
# Select USIM profile
usim_card = UsimCard(scc)
# Check whether we have th AID of USIM, if so select it by its AID
# EF.UST - File Id in ADF USIM : 6f38
sw = select_app("USIM", card)
if sw == '9000':
# Select USIM profile
usim_card = UsimCard(scc)
# EF.EHPLMN
if usim_card.file_exists(EF_USIM_ADF_map['EHPLMN']):
(res, sw) = usim_card.read_ehplmn()
if sw == '9000':
print("EHPLMN:\n%s" % (res))
else:
print("EHPLMN: Can't read, response code = %s" % (sw,))
# EF.EHPLMN
if usim_card.file_exists(EF_USIM_ADF_map['EHPLMN']):
(res, sw) = usim_card.read_ehplmn()
if sw == '9000':
print("EHPLMN:\n%s" % (res))
else:
print("EHPLMN: Can't read, response code = %s" % (sw,))
# EF.UST
try:
if usim_card.file_exists(EF_USIM_ADF_map['UST']):
# res[0] - EF content of UST
# res[1] - Human readable format of services marked available in UST
(res, sw) = usim_card.read_ust()
if sw == '9000':
print("USIM Service Table: %s" % res[0])
print("%s" % res[1])
else:
print("USIM Service Table: Can't read, response code = %s" % (sw,))
except Exception as e:
print("USIM Service Table: Can't read file -- " + str(e))
# EF.UST
try:
if usim_card.file_exists(EF_USIM_ADF_map['UST']):
# res[0] - EF content of UST
# res[1] - Human readable format of services marked available in UST
(res, sw) = usim_card.read_ust()
if sw == '9000':
print("USIM Service Table: %s" % res[0])
print("%s" % res[1])
else:
print("USIM Service Table: Can't read, response code = %s" % (sw,))
except Exception as e:
print("USIM Service Table: Can't read file -- " + str(e))
#EF.ePDGId - Home ePDG Identifier
try:
if usim_card.file_exists(EF_USIM_ADF_map['ePDGId']):
(res, sw) = usim_card.read_epdgid()
if sw == '9000':
print("ePDGId:\n%s" % (len(res) and res or '\tNot available\n',))
else:
print("ePDGId: Can't read, response code = %s" % (sw,))
except Exception as e:
print("ePDGId: Can't read file -- " + str(e))
# EF.ePDGId - Home ePDG Identifier
try:
if usim_card.file_exists(EF_USIM_ADF_map['ePDGId']):
(res, sw) = usim_card.read_epdgid()
if sw == '9000':
print("ePDGId:\n%s" %
(len(res) and res or '\tNot available\n',))
else:
print("ePDGId: Can't read, response code = %s" % (sw,))
except Exception as e:
print("ePDGId: Can't read file -- " + str(e))
#EF.ePDGSelection - ePDG Selection Information
try:
if usim_card.file_exists(EF_USIM_ADF_map['ePDGSelection']):
(res, sw) = usim_card.read_ePDGSelection()
if sw == '9000':
print("ePDGSelection:\n%s" % (res,))
else:
print("ePDGSelection: Can't read, response code = %s" % (sw,))
except Exception as e:
print("ePDGSelection: Can't read file -- " + str(e))
# EF.ePDGSelection - ePDG Selection Information
try:
if usim_card.file_exists(EF_USIM_ADF_map['ePDGSelection']):
(res, sw) = usim_card.read_ePDGSelection()
if sw == '9000':
print("ePDGSelection:\n%s" % (res,))
else:
print("ePDGSelection: Can't read, response code = %s" % (sw,))
except Exception as e:
print("ePDGSelection: Can't read file -- " + str(e))
# Select ISIM application by its AID
sw = select_app("ISIM", card)
if sw == '9000':
# Select USIM profile
isim_card = IsimCard(scc)
# Select ISIM application by its AID
sw = select_app("ISIM", card)
if sw == '9000':
# Select USIM profile
isim_card = IsimCard(scc)
#EF.P-CSCF - P-CSCF Address
try:
if isim_card.file_exists(EF_ISIM_ADF_map['PCSCF']):
res = isim_card.read_pcscf()
print("P-CSCF:\n%s" % (len(res) and res or '\tNot available\n',))
except Exception as e:
print("P-CSCF: Can't read file -- " + str(e))
# EF.P-CSCF - P-CSCF Address
try:
if isim_card.file_exists(EF_ISIM_ADF_map['PCSCF']):
res = isim_card.read_pcscf()
print("P-CSCF:\n%s" %
(len(res) and res or '\tNot available\n',))
except Exception as e:
print("P-CSCF: Can't read file -- " + str(e))
# EF.DOMAIN - Home Network Domain Name e.g. ims.mncXXX.mccXXX.3gppnetwork.org
try:
if isim_card.file_exists(EF_ISIM_ADF_map['DOMAIN']):
(res, sw) = isim_card.read_domain()
if sw == '9000':
print("Home Network Domain Name: %s" % (len(res) and res or 'Not available',))
else:
print("Home Network Domain Name: Can't read, response code = %s" % (sw,))
except Exception as e:
print("Home Network Domain Name: Can't read file -- " + str(e))
# EF.DOMAIN - Home Network Domain Name e.g. ims.mncXXX.mccXXX.3gppnetwork.org
try:
if isim_card.file_exists(EF_ISIM_ADF_map['DOMAIN']):
(res, sw) = isim_card.read_domain()
if sw == '9000':
print("Home Network Domain Name: %s" %
(len(res) and res or 'Not available',))
else:
print(
"Home Network Domain Name: Can't read, response code = %s" % (sw,))
except Exception as e:
print("Home Network Domain Name: Can't read file -- " + str(e))
# EF.IMPI - IMS private user identity
try:
if isim_card.file_exists(EF_ISIM_ADF_map['IMPI']):
(res, sw) = isim_card.read_impi()
if sw == '9000':
print("IMS private user identity: %s" % (len(res) and res or 'Not available',))
else:
print("IMS private user identity: Can't read, response code = %s" % (sw,))
except Exception as e:
print("IMS private user identity: Can't read file -- " + str(e))
# EF.IMPI - IMS private user identity
try:
if isim_card.file_exists(EF_ISIM_ADF_map['IMPI']):
(res, sw) = isim_card.read_impi()
if sw == '9000':
print("IMS private user identity: %s" %
(len(res) and res or 'Not available',))
else:
print(
"IMS private user identity: Can't read, response code = %s" % (sw,))
except Exception as e:
print("IMS private user identity: Can't read file -- " + str(e))
# EF.IMPU - IMS public user identity
try:
if isim_card.file_exists(EF_ISIM_ADF_map['IMPU']):
res = isim_card.read_impu()
print("IMS public user identity:\n%s" % (len(res) and res or '\tNot available\n',))
except Exception as e:
print("IMS public user identity: Can't read file -- " + str(e))
# EF.IMPU - IMS public user identity
try:
if isim_card.file_exists(EF_ISIM_ADF_map['IMPU']):
res = isim_card.read_impu()
print("IMS public user identity:\n%s" %
(len(res) and res or '\tNot available\n',))
except Exception as e:
print("IMS public user identity: Can't read file -- " + str(e))
# EF.UICCIARI - UICC IARI
try:
if isim_card.file_exists(EF_ISIM_ADF_map['UICCIARI']):
res = isim_card.read_iari()
print("UICC IARI:\n%s" % (len(res) and res or '\tNot available\n',))
except Exception as e:
print("UICC IARI: Can't read file -- " + str(e))
# EF.UICCIARI - UICC IARI
try:
if isim_card.file_exists(EF_ISIM_ADF_map['UICCIARI']):
res = isim_card.read_iari()
print("UICC IARI:\n%s" %
(len(res) and res or '\tNot available\n',))
except Exception as e:
print("UICC IARI: Can't read file -- " + str(e))
# EF.IST
(res, sw) = card.read_binary('6f07')
if sw == '9000':
print("ISIM Service Table: %s" % res)
# Print those which are available
print("%s" % dec_st(res, table="isim"))
else:
print("ISIM Service Table: Can't read, response code = %s" % (sw,))
# EF.IST
(res, sw) = card.read_binary('6f07')
if sw == '9000':
print("ISIM Service Table: %s" % res)
# Print those which are available
print("%s" % dec_st(res, table="isim"))
else:
print("ISIM Service Table: Can't read, response code = %s" % (sw,))
# Done for this card and maybe for everything ?
print("Done !\n")
# Done for this card and maybe for everything ?
print("Done !\n")

File diff suppressed because it is too large Load Diff

View File

@ -1 +0,0 @@

View File

@ -34,29 +34,35 @@ from pySim.tlv import *
# various BER-TLV encoded Data Objects (DOs)
class AidRefDO(BER_TLV_IE, tag=0x4f):
# SEID v1.1 Table 6-3
_construct = HexAdapter(GreedyBytes)
class AidRefEmptyDO(BER_TLV_IE, tag=0xc0):
# SEID v1.1 Table 6-3
pass
class DevAppIdRefDO(BER_TLV_IE, tag=0xc1):
# SEID v1.1 Table 6-4
_construct = HexAdapter(GreedyBytes)
class PkgRefDO(BER_TLV_IE, tag=0xca):
# Android UICC Carrier Privileges specific extension, see https://source.android.com/devices/tech/config/uicc
_construct = Struct('package_name_string'/GreedyString("ascii"))
class RefDO(BER_TLV_IE, tag=0xe1, nested=[AidRefDO,AidRefEmptyDO,DevAppIdRefDO,PkgRefDO]):
class RefDO(BER_TLV_IE, tag=0xe1, nested=[AidRefDO, AidRefEmptyDO, DevAppIdRefDO, PkgRefDO]):
# SEID v1.1 Table 6-5
pass
class ApduArDO(BER_TLV_IE, tag=0xd0):
# SEID v1.1 Table 6-8
def _from_bytes(self, do:bytes):
def _from_bytes(self, do: bytes):
if len(do) == 1:
if do[0] == 0x00:
self.decoded = {'generic_access_rule': 'never'}
@ -76,6 +82,7 @@ class ApduArDO(BER_TLV_IE, tag=0xd0):
'mask': b2h(do[offset+4:offset+8])}
self.decoded = res
return res
def _to_bytes(self):
if 'generic_access_rule' in self.decoded:
if self.decoded['generic_access_rule'] == 'never':
@ -99,94 +106,118 @@ class ApduArDO(BER_TLV_IE, tag=0xd0):
res += header_b + mask_b
return res
class NfcArDO(BER_TLV_IE, tag=0xd1):
# SEID v1.1 Table 6-9
_construct = Struct('nfc_event_access_rule'/Enum(Int8ub, never=0, always=1))
_construct = Struct('nfc_event_access_rule' /
Enum(Int8ub, never=0, always=1))
class PermArDO(BER_TLV_IE, tag=0xdb):
# Android UICC Carrier Privileges specific extension, see https://source.android.com/devices/tech/config/uicc
_construct = Struct('permissions'/HexAdapter(Bytes(8)))
class ArDO(BER_TLV_IE, tag=0xe3, nested=[ApduArDO, NfcArDO, PermArDO]):
# SEID v1.1 Table 6-7
pass
class RefArDO(BER_TLV_IE, tag=0xe2, nested=[RefDO, ArDO]):
# SEID v1.1 Table 6-6
pass
class ResponseAllRefArDO(BER_TLV_IE, tag=0xff40, nested=[RefArDO]):
# SEID v1.1 Table 4-2
pass
class ResponseArDO(BER_TLV_IE, tag=0xff50, nested=[ArDO]):
# SEID v1.1 Table 4-3
pass
class ResponseRefreshTagDO(BER_TLV_IE, tag=0xdf20):
# SEID v1.1 Table 4-4
_construct = Struct('refresh_tag'/HexAdapter(Bytes(8)))
class DeviceInterfaceVersionDO(BER_TLV_IE, tag=0xe6):
# SEID v1.1 Table 6-12
_construct = Struct('major'/Int8ub, 'minor'/Int8ub, 'patch'/Int8ub)
class DeviceConfigDO(BER_TLV_IE, tag=0xe4, nested=[DeviceInterfaceVersionDO]):
# SEID v1.1 Table 6-10
pass
class ResponseDeviceConfigDO(BER_TLV_IE, tag=0xff7f, nested=[DeviceConfigDO]):
# SEID v1.1 Table 5-14
pass
class AramConfigDO(BER_TLV_IE, tag=0xe5, nested=[DeviceInterfaceVersionDO]):
# SEID v1.1 Table 6-11
pass
class ResponseAramConfigDO(BER_TLV_IE, tag=0xdf21, nested=[AramConfigDO]):
# SEID v1.1 Table 4-5
pass
class CommandStoreRefArDO(BER_TLV_IE, tag=0xf0, nested=[RefArDO]):
# SEID v1.1 Table 5-2
pass
class CommandDelete(BER_TLV_IE, tag=0xf1, nested=[AidRefDO, AidRefEmptyDO, RefDO, RefArDO]):
# SEID v1.1 Table 5-4
pass
class CommandUpdateRefreshTagDO(BER_TLV_IE, tag=0xf2):
# SEID V1.1 Table 5-6
pass
class CommandRegisterClientAidsDO(BER_TLV_IE, tag=0xf7, nested=[AidRefDO, AidRefEmptyDO]):
# SEID v1.1 Table 5-7
pass
class CommandGet(BER_TLV_IE, tag=0xf3, nested=[AidRefDO, AidRefEmptyDO]):
# SEID v1.1 Table 5-8
pass
class CommandGetAll(BER_TLV_IE, tag=0xf4):
# SEID v1.1 Table 5-9
pass
class CommandGetClientAidsDO(BER_TLV_IE, tag=0xf6):
# SEID v1.1 Table 5-10
pass
class CommandGetNext(BER_TLV_IE, tag=0xf5):
# SEID v1.1 Table 5-11
pass
class CommandGetDeviceConfigDO(BER_TLV_IE, tag=0xf8):
# SEID v1.1 Table 5-12
pass
class ResponseAracAidDO(BER_TLV_IE, tag=0xff70, nested=[AidRefDO, AidRefEmptyDO]):
# SEID v1.1 Table 5-13
pass
class BlockDO(BER_TLV_IE, tag=0xe7):
# SEID v1.1 Table 6-13
_construct = Struct('offset'/Int16ub, 'length'/Int8ub)
@ -197,11 +228,15 @@ class GetCommandDoCollection(TLV_IE_Collection, nested=[RefDO, DeviceConfigDO]):
pass
# SEID v1.1 Table 4-2
class GetResponseDoCollection(TLV_IE_Collection, nested=[ResponseAllRefArDO, ResponseArDO,
ResponseRefreshTagDO, ResponseAramConfigDO]):
pass
# SEID v1.1 Table 5-1
class StoreCommandDoCollection(TLV_IE_Collection,
nested=[BlockDO, CommandStoreRefArDO, CommandDelete,
CommandUpdateRefreshTagDO, CommandRegisterClientAidsDO,
@ -215,6 +250,7 @@ class StoreResponseDoCollection(TLV_IE_Collection,
nested=[ResponseAllRefArDO, ResponseAracAidDO, ResponseDeviceConfigDO]):
pass
class ADF_ARAM(CardADF):
def __init__(self, aid='a00000015141434c00', name='ADF.ARA-M', fid=None, sfid=None,
desc='ARA-M Application'):
@ -224,7 +260,7 @@ class ADF_ARAM(CardADF):
self.add_files(files)
@staticmethod
def xceive_apdu_tlv(tp, hdr:Hexstr, cmd_do, resp_cls, exp_sw='9000'):
def xceive_apdu_tlv(tp, hdr: Hexstr, cmd_do, resp_cls, exp_sw='9000'):
"""Transceive an APDU with the card, transparently encoding the command data from TLV
and decoding the response data tlv."""
if cmd_do:
@ -259,7 +295,8 @@ class ADF_ARAM(CardADF):
@staticmethod
def get_config(tp, v_major=0, v_minor=0, v_patch=1):
cmd_do = DeviceConfigDO()
cmd_do.from_dict([{'DeviceInterfaceVersionDO': {'major': v_major, 'minor': v_minor, 'patch': v_patch }}])
cmd_do.from_dict([{'DeviceInterfaceVersionDO': {
'major': v_major, 'minor': v_minor, 'patch': v_patch}}])
return ADF_ARAM.xceive_apdu_tlv(tp, '80cadf21', cmd_do, ResponseAramConfigDO)
@with_default_category('Application-Specific Commands')
@ -281,20 +318,30 @@ class ADF_ARAM(CardADF):
store_ref_ar_do_parse = argparse.ArgumentParser()
# REF-DO
store_ref_ar_do_parse.add_argument('--device-app-id', required=True, help='Identifies the specific device application that the rule appplies to. Hash of Certificate of Application Provider, or UUID. (20/32 hex bytes)')
store_ref_ar_do_parse.add_argument(
'--device-app-id', required=True, help='Identifies the specific device application that the rule appplies to. Hash of Certificate of Application Provider, or UUID. (20/32 hex bytes)')
aid_grp = store_ref_ar_do_parse.add_mutually_exclusive_group()
aid_grp.add_argument('--aid', help='Identifies the specific SE application for which rules are to be stored. Can be a partial AID, containing for example only the RID. (5-16 hex bytes)')
aid_grp.add_argument('--aid-empty', action='store_true', help='No specific SE application, applies to all applications')
store_ref_ar_do_parse.add_argument('--pkg-ref', help='Full Android Java package name (up to 127 chars ASCII)')
aid_grp.add_argument(
'--aid', help='Identifies the specific SE application for which rules are to be stored. Can be a partial AID, containing for example only the RID. (5-16 hex bytes)')
aid_grp.add_argument('--aid-empty', action='store_true',
help='No specific SE application, applies to all applications')
store_ref_ar_do_parse.add_argument(
'--pkg-ref', help='Full Android Java package name (up to 127 chars ASCII)')
# AR-DO
apdu_grp = store_ref_ar_do_parse.add_mutually_exclusive_group()
apdu_grp.add_argument('--apdu-never', action='store_true', help='APDU access is not allowed')
apdu_grp.add_argument('--apdu-always', action='store_true', help='APDU access is allowed')
apdu_grp.add_argument('--apdu-filter', help='APDU filter: 4 byte CLA/INS/P1/P2 followed by 4 byte mask (8 hex bytes)')
apdu_grp.add_argument(
'--apdu-never', action='store_true', help='APDU access is not allowed')
apdu_grp.add_argument(
'--apdu-always', action='store_true', help='APDU access is allowed')
apdu_grp.add_argument(
'--apdu-filter', help='APDU filter: 4 byte CLA/INS/P1/P2 followed by 4 byte mask (8 hex bytes)')
nfc_grp = store_ref_ar_do_parse.add_mutually_exclusive_group()
nfc_grp.add_argument('--nfc-always', action='store_true', help='NFC event access is allowed')
nfc_grp.add_argument('--nfc-never', action='store_true', help='NFC event access is not allowed')
store_ref_ar_do_parse.add_argument('--android-permissions', help='Android UICC Carrier Privilege Permissions (8 hex bytes)')
nfc_grp.add_argument('--nfc-always', action='store_true',
help='NFC event access is allowed')
nfc_grp.add_argument('--nfc-never', action='store_true',
help='NFC event access is not allowed')
store_ref_ar_do_parse.add_argument(
'--android-permissions', help='Android UICC Carrier Privilege Permissions (8 hex bytes)')
@cmd2.with_argparser(store_ref_ar_do_parse)
def do_aram_store_ref_ar_do(self, opts):
@ -323,7 +370,7 @@ class ADF_ARAM(CardADF):
ar_do_content += [{'NfcArDO': {'nfc_event_access_rule': 'never'}}]
if opts.android_permissions:
ar_do_content += [{'PermArDO': {'permissions': opts.android_permissions}}]
d = [{'RefArDO': [{ 'RefDO': ref_do_content}, {'ArDO': ar_do_content }]}]
d = [{'RefArDO': [{'RefDO': ref_do_content}, {'ArDO': ar_do_content}]}]
csrado = CommandStoreRefArDO()
csrado.from_dict(d)
res_do = ADF_ARAM.store_data(self._cmd.card._scc._tp, csrado)
@ -359,6 +406,7 @@ sw_aram = {
}
}
class CardApplicationARAM(CardApplication):
def __init__(self):
super().__init__('ARA-M', adf=ADF_ARAM(), sw=sw_aram)
super().__init__('ARA-M', adf=ADF_ARAM(), sw=sw_aram)

View File

@ -30,116 +30,118 @@ import subprocess
import sys
import yaml
class CardHandlerBase:
"""Abstract base class representing a mechanism for card insertion/removal."""
"""Abstract base class representing a mechanism for card insertion/removal."""
def __init__(self, sl:LinkBase):
self.sl = sl
def __init__(self, sl: LinkBase):
self.sl = sl
def get(self, first:bool = False):
"""Method called when pySim needs a new card to be inserted.
def get(self, first: bool = False):
"""Method called when pySim needs a new card to be inserted.
Args:
first : set to true when the get method is called the
first time. This is required to prevent blocking
when a card is already inserted into the reader.
The reader API would not recognize that card as
"new card" until it would be removed and re-inserted
again.
"""
print("Ready for Programming: ", end='')
self._get(first)
Args:
first : set to true when the get method is called the
first time. This is required to prevent blocking
when a card is already inserted into the reader.
The reader API would not recognize that card as
"new card" until it would be removed and re-inserted
again.
"""
print("Ready for Programming: ", end='')
self._get(first)
def error(self):
"""Method called when pySim failed to program a card. Move card to 'bad' batch."""
print("Programming failed: ", end='')
self._error()
def error(self):
"""Method called when pySim failed to program a card. Move card to 'bad' batch."""
print("Programming failed: ", end='')
self._error()
def done(self):
"""Method called when pySim failed to program a card. Move card to 'good' batch."""
print("Programming successful: ", end='')
self._done()
def done(self):
"""Method called when pySim failed to program a card. Move card to 'good' batch."""
print("Programming successful: ", end='')
self._done()
def _get(self, first:bool = False):
pass
def _get(self, first: bool = False):
pass
def _error(self):
pass
def _error(self):
pass
def _done(self):
pass
def _done(self):
pass
class CardHandler(CardHandlerBase):
"""Manual card handler: User is prompted to insert/remove card from the reader."""
"""Manual card handler: User is prompted to insert/remove card from the reader."""
def _get(self, first:bool = False):
print("Insert card now (or CTRL-C to cancel)")
self.sl.wait_for_card(newcardonly=not first)
def _get(self, first: bool = False):
print("Insert card now (or CTRL-C to cancel)")
self.sl.wait_for_card(newcardonly=not first)
def _error(self):
print("Remove card from reader")
print("")
def _error(self):
print("Remove card from reader")
print("")
def _done(self):
print("Remove card from reader")
print("")
def _done(self):
print("Remove card from reader")
print("")
class CardHandlerAuto(CardHandlerBase):
"""Automatic card handler: A machine is used to handle the cards."""
"""Automatic card handler: A machine is used to handle the cards."""
verbose = True
verbose = True
def __init__(self, sl:LinkBase, config_file:str):
super().__init__(sl)
print("Card handler Config-file: " + str(config_file))
with open(config_file) as cfg:
self.cmds = yaml.load(cfg, Loader=yaml.FullLoader)
self.verbose = (self.cmds.get('verbose') == True)
def __init__(self, sl: LinkBase, config_file: str):
super().__init__(sl)
print("Card handler Config-file: " + str(config_file))
with open(config_file) as cfg:
self.cmds = yaml.load(cfg, Loader=yaml.FullLoader)
self.verbose = (self.cmds.get('verbose') == True)
def __print_outout(self, out):
print("")
print("Card handler output:")
print("---------------------8<---------------------")
stdout = out[0].strip()
if len(stdout) > 0:
print("stdout:")
print(stdout)
stderr = out[1].strip()
if len(stderr) > 0:
print("stderr:")
print(stderr)
print("---------------------8<---------------------")
print("")
def __print_outout(self, out):
print("")
print("Card handler output:")
print("---------------------8<---------------------")
stdout = out[0].strip()
if len(stdout) > 0:
print("stdout:")
print(stdout)
stderr = out[1].strip()
if len(stderr) > 0:
print("stderr:")
print(stderr)
print("---------------------8<---------------------")
print("")
def __exec_cmd(self, command):
print("Card handler Commandline: " + str(command))
def __exec_cmd(self, command):
print("Card handler Commandline: " + str(command))
proc = subprocess.Popen([command], stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True)
out = proc.communicate()
rc = proc.returncode
proc = subprocess.Popen(
[command], stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True)
out = proc.communicate()
rc = proc.returncode
if rc != 0 or self.verbose:
self.__print_outout(out)
if rc != 0 or self.verbose:
self.__print_outout(out)
if rc != 0:
print("")
print("Error: Card handler failure! (rc=" + str(rc) + ")")
sys.exit(rc)
if rc != 0:
print("")
print("Error: Card handler failure! (rc=" + str(rc) + ")")
sys.exit(rc)
def _get(self, first:bool = False):
print("Transporting card into the reader-bay...")
self.__exec_cmd(self.cmds['get'])
if self.sl:
self.sl.connect()
def _get(self, first: bool = False):
print("Transporting card into the reader-bay...")
self.__exec_cmd(self.cmds['get'])
if self.sl:
self.sl.connect()
def _error(self):
print("Transporting card to the error-bin...")
self.__exec_cmd(self.cmds['error'])
print("")
def _error(self):
print("Transporting card to the error-bin...")
self.__exec_cmd(self.cmds['error'])
print("")
def _done(self):
print("Transporting card into the collector bin...")
self.__exec_cmd(self.cmds['done'])
print("")
def _done(self):
print("Transporting card into the collector bin...")
self.__exec_cmd(self.cmds['done'])
print("")

View File

@ -33,136 +33,142 @@ from typing import List, Dict, Optional
import abc
import csv
card_key_providers = [] # type: List['CardKeyProvider']
card_key_providers = [] # type: List['CardKeyProvider']
class CardKeyProvider(abc.ABC):
"""Base class, not containing any concrete implementation."""
"""Base class, not containing any concrete implementation."""
VALID_FIELD_NAMES = ['ICCID', 'ADM1', 'IMSI', 'PIN1', 'PIN2', 'PUK1', 'PUK2']
VALID_FIELD_NAMES = ['ICCID', 'ADM1',
'IMSI', 'PIN1', 'PIN2', 'PUK1', 'PUK2']
# check input parameters, but do nothing concrete yet
def _verify_get_data(self, fields:List[str]=[], key:str='ICCID', value:str="") -> Dict[str,str]:
"""Verify multiple fields for identified card.
# check input parameters, but do nothing concrete yet
def _verify_get_data(self, fields: List[str] = [], key: str = 'ICCID', value: str = "") -> Dict[str, str]:
"""Verify multiple fields for identified card.
Args:
fields : list of valid field names such as 'ADM1', 'PIN1', ... which are to be obtained
key : look-up key to identify card data, such as 'ICCID'
value : value for look-up key to identify card data
Returns:
dictionary of {field, value} strings for each requested field from 'fields'
"""
for f in fields:
if (f not in self.VALID_FIELD_NAMES):
raise ValueError("Requested field name '%s' is not a valid field name, valid field names are: %s" %
(f, str(self.VALID_FIELD_NAMES)))
Args:
fields : list of valid field names such as 'ADM1', 'PIN1', ... which are to be obtained
key : look-up key to identify card data, such as 'ICCID'
value : value for look-up key to identify card data
Returns:
dictionary of {field, value} strings for each requested field from 'fields'
"""
for f in fields:
if (f not in self.VALID_FIELD_NAMES):
raise ValueError("Requested field name '%s' is not a valid field name, valid field names are: %s" %
(f, str(self.VALID_FIELD_NAMES)))
if (key not in self.VALID_FIELD_NAMES):
raise ValueError("Key field name '%s' is not a valid field name, valid field names are: %s" %
(key, str(self.VALID_FIELD_NAMES)))
if (key not in self.VALID_FIELD_NAMES):
raise ValueError("Key field name '%s' is not a valid field name, valid field names are: %s" %
(key, str(self.VALID_FIELD_NAMES)))
return {}
return {}
def get_field(self, field:str, key:str='ICCID', value:str="") -> Optional[str]:
"""get a single field from CSV file using a specified key/value pair"""
fields = [field]
result = self.get(fields, key, value)
return result.get(field)
def get_field(self, field: str, key: str = 'ICCID', value: str = "") -> Optional[str]:
"""get a single field from CSV file using a specified key/value pair"""
fields = [field]
result = self.get(fields, key, value)
return result.get(field)
@abc.abstractmethod
def get(self, fields:List[str], key:str, value:str) -> Dict[str,str]:
"""Get multiple card-individual fields for identified card.
@abc.abstractmethod
def get(self, fields: List[str], key: str, value: str) -> Dict[str, str]:
"""Get multiple card-individual fields for identified card.
Args:
fields : list of valid field names such as 'ADM1', 'PIN1', ... which are to be obtained
key : look-up key to identify card data, such as 'ICCID'
value : value for look-up key to identify card data
Returns:
dictionary of {field, value} strings for each requested field from 'fields'
"""
Args:
fields : list of valid field names such as 'ADM1', 'PIN1', ... which are to be obtained
key : look-up key to identify card data, such as 'ICCID'
value : value for look-up key to identify card data
Returns:
dictionary of {field, value} strings for each requested field from 'fields'
"""
class CardKeyProviderCsv(CardKeyProvider):
"""Card key provider implementation that allows to query against a specified CSV file"""
csv_file = None
filename = None
"""Card key provider implementation that allows to query against a specified CSV file"""
csv_file = None
filename = None
def __init__(self, filename:str):
"""
Args:
filename : file name (path) of CSV file containing card-individual key/data
"""
self.csv_file = open(filename, 'r')
if not self.csv_file:
raise RuntimeError("Could not open CSV file '%s'" % filename)
self.filename = filename
def __init__(self, filename: str):
"""
Args:
filename : file name (path) of CSV file containing card-individual key/data
"""
self.csv_file = open(filename, 'r')
if not self.csv_file:
raise RuntimeError("Could not open CSV file '%s'" % filename)
self.filename = filename
def get(self, fields:List[str], key:str, value:str) -> Dict[str,str]:
super()._verify_get_data(fields, key, value)
def get(self, fields: List[str], key: str, value: str) -> Dict[str, str]:
super()._verify_get_data(fields, key, value)
self.csv_file.seek(0)
cr = csv.DictReader(self.csv_file)
if not cr:
raise RuntimeError("Could not open DictReader for CSV-File '%s'" % self.filename)
cr.fieldnames = [ field.upper() for field in cr.fieldnames ]
self.csv_file.seek(0)
cr = csv.DictReader(self.csv_file)
if not cr:
raise RuntimeError(
"Could not open DictReader for CSV-File '%s'" % self.filename)
cr.fieldnames = [field.upper() for field in cr.fieldnames]
rc = {}
for row in cr:
if row[key] == value:
for f in fields:
if f in row:
rc.update({f : row[f]})
else:
raise RuntimeError("CSV-File '%s' lacks column '%s'" %
(self.filename, f))
return rc
rc = {}
for row in cr:
if row[key] == value:
for f in fields:
if f in row:
rc.update({f: row[f]})
else:
raise RuntimeError("CSV-File '%s' lacks column '%s'" %
(self.filename, f))
return rc
def card_key_provider_register(provider:CardKeyProvider, provider_list=card_key_providers):
"""Register a new card key provider.
def card_key_provider_register(provider: CardKeyProvider, provider_list=card_key_providers):
"""Register a new card key provider.
Args:
provider : the to-be-registered provider
provider_list : override the list of providers from the global default
"""
if not isinstance(provider, CardKeyProvider):
raise ValueError("provider is not a card data provier")
provider_list.append(provider)
Args:
provider : the to-be-registered provider
provider_list : override the list of providers from the global default
"""
if not isinstance(provider, CardKeyProvider):
raise ValueError("provider is not a card data provier")
provider_list.append(provider)
def card_key_provider_get(fields, key:str, value:str, provider_list=card_key_providers) -> Dict[str,str]:
"""Query all registered card data providers for card-individual [key] data.
def card_key_provider_get(fields, key: str, value: str, provider_list=card_key_providers) -> Dict[str, str]:
"""Query all registered card data providers for card-individual [key] data.
Args:
fields : list of valid field names such as 'ADM1', 'PIN1', ... which are to be obtained
key : look-up key to identify card data, such as 'ICCID'
value : value for look-up key to identify card data
provider_list : override the list of providers from the global default
Returns:
dictionary of {field, value} strings for each requested field from 'fields'
"""
for p in provider_list:
if not isinstance(p, CardKeyProvider):
raise ValueError("provider list contains element which is not a card data provier")
result = p.get(fields, key, value)
if result:
return result
return {}
Args:
fields : list of valid field names such as 'ADM1', 'PIN1', ... which are to be obtained
key : look-up key to identify card data, such as 'ICCID'
value : value for look-up key to identify card data
provider_list : override the list of providers from the global default
Returns:
dictionary of {field, value} strings for each requested field from 'fields'
"""
for p in provider_list:
if not isinstance(p, CardKeyProvider):
raise ValueError(
"provider list contains element which is not a card data provier")
result = p.get(fields, key, value)
if result:
return result
return {}
def card_key_provider_get_field(field:str, key:str, value:str, provider_list=card_key_providers) -> Optional[str]:
"""Query all registered card data providers for a single field.
def card_key_provider_get_field(field: str, key: str, value: str, provider_list=card_key_providers) -> Optional[str]:
"""Query all registered card data providers for a single field.
Args:
field : name valid field such as 'ADM1', 'PIN1', ... which is to be obtained
key : look-up key to identify card data, such as 'ICCID'
value : value for look-up key to identify card data
provider_list : override the list of providers from the global default
Returns:
dictionary of {field, value} strings for the requested field
"""
for p in provider_list:
if not isinstance(p, CardKeyProvider):
raise ValueError("provider list contains element which is not a card data provier")
result = p.get_field(field, key, value)
if result:
return result
return None
Args:
field : name valid field such as 'ADM1', 'PIN1', ... which is to be obtained
key : look-up key to identify card data, such as 'ICCID'
value : value for look-up key to identify card data
provider_list : override the list of providers from the global default
Returns:
dictionary of {field, value} strings for the requested field
"""
for p in provider_list:
if not isinstance(p, CardKeyProvider):
raise ValueError(
"provider list contains element which is not a card data provier")
result = p.get_field(field, key, value)
if result:
return result
return None

File diff suppressed because it is too large Load Diff

View File

@ -24,34 +24,48 @@ from construct import *
# Tag values as per TS 101 220 Table 7.23
# TS 102 223 Section 8.1
class Address(COMPR_TLV_IE, tag=0x06):
_construct = Struct('ton_npi'/Int8ub,
'call_number'/BcdAdapter(Bytes(this._.total_len-1)))
# TS 102 223 Section 8.2
class AlphaIdentifier(COMPR_TLV_IE, tag=0x05):
# FIXME: like EF.ADN
pass
# TS 102 223 Section 8.3
class Subaddress(COMPR_TLV_IE, tag=0x08):
pass
# TS 102 223 Section 8.4
class CapabilityConfigParams(COMPR_TLV_IE, tag=0x07):
pass
# TS 31.111 Section 8.5
class CBSPage(COMPR_TLV_IE, tag=0x0C):
pass
# TS 102 223 Section 8.6
class CommandDetails(COMPR_TLV_IE, tag=0x01):
_construct = Struct('command_number'/Int8ub,
'type_of_command'/Int8ub,
'command_qualifier'/Int8ub)
# TS 102 223 Section 8.7
class DeviceIdentities(COMPR_TLV_IE, tag=0x82):
DEV_IDS = bidict({
0x01: 'keypad',
@ -90,8 +104,9 @@ class DeviceIdentities(COMPR_TLV_IE, tag=0x82):
0x81: 'uicc',
0x82: 'terminal',
0x83: 'network',
})
def _from_bytes(self, do:bytes):
})
def _from_bytes(self, do: bytes):
return {'source_dev_id': self.DEV_IDS[do[0]], 'dest_dev_id': self.DEV_IDS[do[1]]}
def _to_bytes(self):
@ -100,200 +115,219 @@ class DeviceIdentities(COMPR_TLV_IE, tag=0x82):
return bytes([src, dst])
# TS 102 223 Section 8.8
class Duration(COMPR_TLV_IE, tag=0x04):
_construct = Struct('time_unit'/Int8ub,
'time_interval'/Int8ub)
# TS 102 223 Section 8.9
class Item(COMPR_TLV_IE, tag=0x0f):
_construct = Struct('identifier'/Int8ub,
'text_string'/GsmStringAdapter(GreedyBytes))
# TS 102 223 Section 8.10
class ItemIdentifier(COMPR_TLV_IE, tag=0x10):
_construct = Struct('identifier'/Int8ub)
# TS 102 223 Section 8.11
class ResponseLength(COMPR_TLV_IE, tag=0x11):
_construct = Struct('minimum_length'/Int8ub,
'maximum_length'/Int8ub)
# TS 102 223 Section 8.12
class Result(COMPR_TLV_IE, tag=0x03):
_construct = Struct('general_result'/Int8ub,
'additional_information'/HexAdapter(GreedyBytes))
# TS 102 223 Section 8.13 + TS 31.111 Section 8.13
class SMS_TPDU(COMPR_TLV_IE, tag=0x8B):
_construct = Struct('tpdu'/HexAdapter(GreedyBytes))
# TS 102 223 Section 8.15
class TextString(COMPR_TLV_IE, tag=0x0d):
_construct = Struct('dcs'/Int8ub,
'text_string'/HexAdapter(GreedyBytes))
# TS 102 223 Section 8.16
class Tone(COMPR_TLV_IE, tag=0x0e):
_construct = Struct('tone'/Int8ub)
# TS 31 111 Section 8.17
class USSDString(COMPR_TLV_IE, tag=0x0a):
_construct = Struct('dcs'/Int8ub,
'ussd_string'/HexAdapter(GreedyBytes))
# TS 101 220 Table 7.17
class ProactiveCommand(BER_TLV_IE, tag=0xD0):
pass
# TS 101 220 Table 7.17 + 31.111 7.1.1.2
class SMSPPDownload(BER_TLV_IE, tag=0xD1,
nested=[DeviceIdentities, Address, SMS_TPDU]):
pass
# TS 101 220 Table 7.17 + 31.111 7.1.1.3
class SMSCBDownload(BER_TLV_IE, tag=0xD2,
nested=[DeviceIdentities, CBSPage]):
pass
class USSDDownload(BER_TLV_IE, tag=0xD9,
nested=[DeviceIdentities, USSDString]):
nested=[DeviceIdentities, USSDString]):
pass
# reasonable default for playing with OTA
# 010203040506070809101112131415161718192021222324252627282930313233
#'7fe1e10e000000000000001f43000000ff00000000000000000000000000000000'
# '7fe1e10e000000000000001f43000000ff00000000000000000000000000000000'
# TS 102 223 Section 5.2
term_prof_bits = {
# first byte
1: 'Profile download',
2: 'SMS-PP data download',
3: 'Cell Broadcast data download',
4: 'Menu selection',
5: 'SMS-PP data download',
6: 'Timer expiration',
7: 'USSD string DO support in CC by USIM',
8: 'Call Control by NAA',
# first byte
1: 'Profile download',
2: 'SMS-PP data download',
3: 'Cell Broadcast data download',
4: 'Menu selection',
5: 'SMS-PP data download',
6: 'Timer expiration',
7: 'USSD string DO support in CC by USIM',
8: 'Call Control by NAA',
# first byte
9: 'Command result',
10: 'Call Control by NAA',
11: 'Call Control by NAA',
12: 'MO short message control support',
13: 'Call Control by NAA',
14: 'UCS2 Entry supported',
15: 'UCS2 Display supported',
16: 'Display Text',
# first byte
9: 'Command result',
10: 'Call Control by NAA',
11: 'Call Control by NAA',
12: 'MO short message control support',
13: 'Call Control by NAA',
14: 'UCS2 Entry supported',
15: 'UCS2 Display supported',
16: 'Display Text',
# third byte
17: 'Proactive UICC: DISPLAY TEXT',
18: 'Proactive UICC: GET INKEY',
19: 'Proactive UICC: GET INPUT',
20: 'Proactive UICC: MORE TIME',
21: 'Proactive UICC: PLAY TONE',
22: 'Proactive UICC: POLL INTERVAL',
23: 'Proactive UICC: POLLING OFF',
24: 'Proactive UICC: REFRESH',
# third byte
17: 'Proactive UICC: DISPLAY TEXT',
18: 'Proactive UICC: GET INKEY',
19: 'Proactive UICC: GET INPUT',
20: 'Proactive UICC: MORE TIME',
21: 'Proactive UICC: PLAY TONE',
22: 'Proactive UICC: POLL INTERVAL',
23: 'Proactive UICC: POLLING OFF',
24: 'Proactive UICC: REFRESH',
# fourth byte
25: 'Proactive UICC: SELECT ITEM',
26: 'Proactive UICC: SEND SHORT MESSAGE with 3GPP-SMS-TPDU',
27: 'Proactive UICC: SEND SS',
28: 'Proactive UICC: SEND USSD',
29: 'Proactive UICC: SET UP CALL',
30: 'Proactive UICC: SET UP MENU',
31: 'Proactive UICC: PROVIDE LOCAL INFORMATION (MCC, MNC, LAC, Cell ID & IMEI)',
32: 'Proactive UICC: PROVIDE LOCAL INFORMATION (NMR)',
# fourth byte
25: 'Proactive UICC: SELECT ITEM',
26: 'Proactive UICC: SEND SHORT MESSAGE with 3GPP-SMS-TPDU',
27: 'Proactive UICC: SEND SS',
28: 'Proactive UICC: SEND USSD',
29: 'Proactive UICC: SET UP CALL',
30: 'Proactive UICC: SET UP MENU',
31: 'Proactive UICC: PROVIDE LOCAL INFORMATION (MCC, MNC, LAC, Cell ID & IMEI)',
32: 'Proactive UICC: PROVIDE LOCAL INFORMATION (NMR)',
# fifth byte
33: 'Proactive UICC: SET UP EVENT LIST',
34: 'Event: MT call',
35: 'Event: Call connected',
36: 'Event: Call disconnected',
37: 'Event: Location status',
38: 'Event: User activity',
39: 'Event: Idle screen available',
40: 'Event: Card reader status',
# fifth byte
33: 'Proactive UICC: SET UP EVENT LIST',
34: 'Event: MT call',
35: 'Event: Call connected',
36: 'Event: Call disconnected',
37: 'Event: Location status',
38: 'Event: User activity',
39: 'Event: Idle screen available',
40: 'Event: Card reader status',
# sixth byte
41: 'Event: Language selection',
42: 'Event: Browser Termination',
43: 'Event: Data aailable',
44: 'Event: Channel status',
45: 'Event: Access Technology Change',
46: 'Event: Display parameters changed',
47: 'Event: Local Connection',
48: 'Event: Network Search Mode Change',
# sixth byte
41: 'Event: Language selection',
42: 'Event: Browser Termination',
43: 'Event: Data aailable',
44: 'Event: Channel status',
45: 'Event: Access Technology Change',
46: 'Event: Display parameters changed',
47: 'Event: Local Connection',
48: 'Event: Network Search Mode Change',
# seventh byte
49: 'Proactive UICC: POWER ON CARD',
50: 'Proactive UICC: POWER OFF CARD',
51: 'Proactive UICC: PERFORM CARD RESET',
52: 'Proactive UICC: GET READER STATUS (Card reader status)',
53: 'Proactive UICC: GET READER STATUS (Card reader identifier)',
# RFU: 3 bit (54,55,56)
# seventh byte
49: 'Proactive UICC: POWER ON CARD',
50: 'Proactive UICC: POWER OFF CARD',
51: 'Proactive UICC: PERFORM CARD RESET',
52: 'Proactive UICC: GET READER STATUS (Card reader status)',
53: 'Proactive UICC: GET READER STATUS (Card reader identifier)',
# RFU: 3 bit (54,55,56)
# eighth byte
57: 'Proactive UICC: TIMER MANAGEMENT (start, stop)',
58: 'Proactive UICC: TIMER MANAGEMENT (get current value)',
59: 'Proactive UICC: PROVIDE LOCAL INFORMATION (date, time and time zone)',
60: 'GET INKEY',
61: 'SET UP IDLE MODE TEXT',
62: 'RUN AT COMMAND',
63: 'SETUP CALL',
64: 'Call Control by NAA',
# eighth byte
57: 'Proactive UICC: TIMER MANAGEMENT (start, stop)',
58: 'Proactive UICC: TIMER MANAGEMENT (get current value)',
59: 'Proactive UICC: PROVIDE LOCAL INFORMATION (date, time and time zone)',
60: 'GET INKEY',
61: 'SET UP IDLE MODE TEXT',
62: 'RUN AT COMMAND',
63: 'SETUP CALL',
64: 'Call Control by NAA',
# ninth byte
65: 'DISPLAY TEXT',
66: 'SEND DTMF command',
67: 'Proactive UICC: PROVIDE LOCAL INFORMATION (NMR)',
68: 'Proactive UICC: PROVIDE LOCAL INFORMATION (language)',
69: 'Proactive UICC: PROVIDE LOCAL INFORMATION (Timing Advance)',
70: 'Proactive UICC: LANGUAGE NOTIFICATION',
71: 'Proactive UICC: LAUNCH BROWSER',
72: 'Proactive UICC: PROVIDE LOCAL INFORMATION (Access Technology)',
# ninth byte
65: 'DISPLAY TEXT',
66: 'SEND DTMF command',
67: 'Proactive UICC: PROVIDE LOCAL INFORMATION (NMR)',
68: 'Proactive UICC: PROVIDE LOCAL INFORMATION (language)',
69: 'Proactive UICC: PROVIDE LOCAL INFORMATION (Timing Advance)',
70: 'Proactive UICC: LANGUAGE NOTIFICATION',
71: 'Proactive UICC: LAUNCH BROWSER',
72: 'Proactive UICC: PROVIDE LOCAL INFORMATION (Access Technology)',
# tenth byte
73: 'Soft keys support for SELECT ITEM',
74: 'Soft keys support for SET UP MENU ITEM',
# RFU: 6 bit (75-80)
# tenth byte
73: 'Soft keys support for SELECT ITEM',
74: 'Soft keys support for SET UP MENU ITEM',
# RFU: 6 bit (75-80)
# eleventh byte: max number of soft keys as 8bit value (81..88)
# eleventh byte: max number of soft keys as 8bit value (81..88)
# twelfth byte
89: 'Proactive UICC: OPEN CHANNEL',
90: 'Proactive UICC: CLOSE CHANNEL',
91: 'Proactive UICC: RECEIVE DATA',
92: 'Proactive UICC: SEND DATA',
93: 'Proactive UICC: GET CHANNEL STATUS',
94: 'Proactive UICC: SERVICE SEARCH',
95: 'Proactive UICC: GET SERVICE INFORMATION',
96: 'Proactive UICC: DECLARE SERVICE',
# twelfth byte
89: 'Proactive UICC: OPEN CHANNEL',
90: 'Proactive UICC: CLOSE CHANNEL',
91: 'Proactive UICC: RECEIVE DATA',
92: 'Proactive UICC: SEND DATA',
93: 'Proactive UICC: GET CHANNEL STATUS',
94: 'Proactive UICC: SERVICE SEARCH',
95: 'Proactive UICC: GET SERVICE INFORMATION',
96: 'Proactive UICC: DECLARE SERVICE',
# thirteenth byte
97: 'BIP supported Bearer: CSD',
98: 'BIP supported Bearer: GPRS',
99: 'BIP supported Bearer: Bluetooth',
100: 'BIP supported Bearer: IrDA',
101: 'BIP supported Bearer: RS232',
# 3 bits: number of channels supported (102..104)
# thirteenth byte
97: 'BIP supported Bearer: CSD',
98: 'BIP supported Bearer: GPRS',
99: 'BIP supported Bearer: Bluetooth',
100: 'BIP supported Bearer: IrDA',
101: 'BIP supported Bearer: RS232',
# 3 bits: number of channels supported (102..104)
# fourtheenth byte (screen height)
# fifteenth byte (screen width)
# sixeenth byte (screen effects)
# seventeenth byte (BIP supported bearers)
129: 'BIP: TCP, UICC in client mode, remote connection',
130: 'BIP: UDP, UICC in client mode, remote connection',
131: 'BIP: TCP, UICC in server mode',
132: 'BIP: TCP, UICC in client mode, local connection',
133: 'BIP: UDP, UICC in client mode, local connection',
134: 'BIP: direct communication channel',
# 2 bits reserved: 135, 136
# fourtheenth byte (screen height)
# fifteenth byte (screen width)
# sixeenth byte (screen effects)
# seventeenth byte (BIP supported bearers)
129: 'BIP: TCP, UICC in client mode, remote connection',
130: 'BIP: UDP, UICC in client mode, remote connection',
131: 'BIP: TCP, UICC in server mode',
132: 'BIP: TCP, UICC in client mode, local connection',
133: 'BIP: UDP, UICC in client mode, local connection',
134: 'BIP: direct communication channel',
# 2 bits reserved: 135, 136
# FIXME: remainder
# FIXME: remainder
}

File diff suppressed because it is too large Load Diff

View File

@ -1,3 +1,5 @@
from construct.lib.containers import Container, ListContainer
from construct.core import EnumIntegerString
import typing
from construct import *
from pySim.utils import b2h, h2b, swap_nibbles
@ -23,18 +25,24 @@ import gsm0338
class HexAdapter(Adapter):
"""convert a bytes() type to a string of hex nibbles."""
def _decode(self, obj, context, path):
return b2h(obj)
def _encode(self, obj, context, path):
return h2b(obj)
class BcdAdapter(Adapter):
"""convert a bytes() type to a string of BCD nibbles."""
def _decode(self, obj, context, path):
return swap_nibbles(b2h(obj))
def _encode(self, obj, context, path):
return h2b(swap_nibbles(obj))
class Rpad(Adapter):
"""
Encoder appends padding bytes (b'\\xff') up to target size.
@ -54,9 +62,11 @@ class Rpad(Adapter):
def _encode(self, obj, context, path):
if len(obj) > self.sizeof():
raise SizeofError("Input ({}) exceeds target size ({})".format(len(obj), self.sizeof()))
raise SizeofError("Input ({}) exceeds target size ({})".format(
len(obj), self.sizeof()))
return obj + self.pattern * (self.sizeof() - len(obj))
class GsmStringAdapter(Adapter):
"""Convert GSM 03.38 encoded bytes to a string."""
@ -71,6 +81,7 @@ class GsmStringAdapter(Adapter):
def _encode(self, obj, context, path):
return obj.encode(self.codec, self.err)
def filter_dict(d, exclude_prefix='_'):
"""filter the input dict to ensure no keys starting with 'exclude_prefix' remain."""
if not isinstance(d, dict):
@ -85,8 +96,6 @@ def filter_dict(d, exclude_prefix='_'):
res[key] = value
return res
from construct.lib.containers import Container, ListContainer
from construct.core import EnumIntegerString
def normalize_construct(c):
"""Convert a construct specific type to a related base type, mostly useful
@ -95,7 +104,7 @@ def normalize_construct(c):
# in the dict: '_io': <_io.BytesIO object at 0x7fdb64e05860> which we cannot json-serialize
c = filter_dict(c)
if isinstance(c, Container) or isinstance(c, dict):
r = {k : normalize_construct(v) for (k, v) in c.items()}
r = {k: normalize_construct(v) for (k, v) in c.items()}
elif isinstance(c, ListContainer):
r = [normalize_construct(x) for x in c]
elif isinstance(c, list):
@ -106,13 +115,15 @@ def normalize_construct(c):
r = c
return r
def parse_construct(c, raw_bin_data:bytes, length:typing.Optional[int]=None, exclude_prefix:str='_'):
def parse_construct(c, raw_bin_data: bytes, length: typing.Optional[int] = None, exclude_prefix: str = '_'):
"""Helper function to wrap around normalize_construct() and filter_dict()."""
if not length:
length = len(raw_bin_data)
parsed = c.parse(raw_bin_data, total_len=length)
return normalize_construct(parsed)
# here we collect some shared / common definitions of data types
LV = Prefixed(Int8ub, HexAdapter(GreedyBytes))
@ -129,6 +140,7 @@ ByteRFU = Default(Byte, __RFU_VALUE)
# Field that packs all remaining Reserved for Future Use (RFU) bytes
GreedyBytesRFU = Default(GreedyBytes, b'')
def BitsRFU(n=1):
'''
Field that packs Reserved for Future Use (RFU) bit(s)
@ -143,6 +155,7 @@ def BitsRFU(n=1):
'''
return Default(BitsInteger(n), __RFU_VALUE)
def BytesRFU(n=1):
'''
Field that packs Reserved for Future Use (RFU) byte(s)
@ -157,6 +170,7 @@ def BytesRFU(n=1):
'''
return Default(Bytes(n), __RFU_VALUE)
def GsmString(n):
'''
GSM 03.38 encoded byte string of fixed length n.

View File

@ -21,34 +21,40 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
class NoCardError(Exception):
"""No card was found in the reader."""
pass
"""No card was found in the reader."""
pass
class ProtocolError(Exception):
"""Some kind of protocol level error interfacing with the card."""
pass
"""Some kind of protocol level error interfacing with the card."""
pass
class ReaderError(Exception):
"""Some kind of general error with the card reader."""
pass
"""Some kind of general error with the card reader."""
pass
class SwMatchError(Exception):
"""Raised when an operation specifies an expected SW but the actual SW from
the card doesn't match."""
def __init__(self, sw_actual:str, sw_expected:str, rs=None):
"""
Args:
sw_actual : the SW we actually received from the card (4 hex digits)
sw_expected : the SW we expected to receive from the card (4 hex digits)
rs : interpreter class to convert SW to string
"""
self.sw_actual = sw_actual
self.sw_expected = sw_expected
self.rs = rs
def __str__(self):
if self.rs:
r = self.rs.interpret_sw(self.sw_actual)
if r:
return "SW match failed! Expected %s and got %s: %s - %s" % (self.sw_expected, self.sw_actual, r[0], r[1])
return "SW match failed! Expected %s and got %s." % (self.sw_expected, self.sw_actual)
"""Raised when an operation specifies an expected SW but the actual SW from
the card doesn't match."""
def __init__(self, sw_actual: str, sw_expected: str, rs=None):
"""
Args:
sw_actual : the SW we actually received from the card (4 hex digits)
sw_expected : the SW we expected to receive from the card (4 hex digits)
rs : interpreter class to convert SW to string
"""
self.sw_actual = sw_actual
self.sw_expected = sw_expected
self.rs = rs
def __str__(self):
if self.rs:
r = self.rs.interpret_sw(self.sw_actual)
if r:
return "SW match failed! Expected %s and got %s: %s - %s" % (self.sw_expected, self.sw_actual, r[0], r[1])
return "SW match failed! Expected %s and got %s." % (self.sw_expected, self.sw_actual)

View File

@ -44,6 +44,7 @@ from pySim.exceptions import *
from pySim.jsonpath import js_path_find, js_path_modify
from pySim.commands import SimCardCommands
class CardFile(object):
"""Base class for all objects in the smart card filesystem.
Serve as a common ancestor to all other file types; rarely used directly.
@ -51,8 +52,8 @@ class CardFile(object):
RESERVED_NAMES = ['..', '.', '/', 'MF']
RESERVED_FIDS = ['3f00']
def __init__(self, fid:str=None, sfid:str=None, name:str=None, desc:str=None,
parent:Optional['CardDF']=None, profile:Optional['CardProfile']=None):
def __init__(self, fid: str = None, sfid: str = None, name: str = None, desc: str = None,
parent: Optional['CardDF'] = None, profile: Optional['CardProfile'] = None):
"""
Args:
fid : File Identifier (4 hex digits)
@ -74,11 +75,11 @@ class CardFile(object):
if self.parent and self.parent != self and self.fid:
self.parent.add_file(self)
self.profile = profile
self.shell_commands = [] # type: List[CommandSet]
self.shell_commands = [] # type: List[CommandSet]
# Note: the basic properties (fid, name, ect.) are verified when
# the file is attached to a parent file. See method add_file() in
# class Card DF
# Note: the basic properties (fid, name, ect.) are verified when
# the file is attached to a parent file. See method add_file() in
# class Card DF
def __str__(self):
if self.name:
@ -86,13 +87,13 @@ class CardFile(object):
else:
return self.fid
def _path_element(self, prefer_name:bool) -> Optional[str]:
def _path_element(self, prefer_name: bool) -> Optional[str]:
if prefer_name and self.name:
return self.name
else:
return self.fid
def fully_qualified_path(self, prefer_name:bool=True) -> List[str]:
def fully_qualified_path(self, prefer_name: bool = True) -> List[str]:
"""Return fully qualified path to file as list of FID or name strings.
Args:
@ -117,7 +118,7 @@ class CardFile(object):
node = node.parent
return cast(CardMF, node)
def _get_self_selectables(self, alias:str=None, flags = []) -> Dict[str, 'CardFile']:
def _get_self_selectables(self, alias: str = None, flags=[]) -> Dict[str, 'CardFile']:
"""Return a dict of {'identifier': self} tuples.
Args:
@ -136,7 +137,7 @@ class CardFile(object):
sels.update({self.name: self})
return sels
def get_selectables(self, flags = []) -> Dict[str, 'CardFile']:
def get_selectables(self, flags=[]) -> Dict[str, 'CardFile']:
"""Return a dict of {'identifier': File} that is selectable from the current file.
Args:
@ -158,11 +159,11 @@ class CardFile(object):
if flags == [] or 'MF' in flags:
mf = self.get_mf()
if mf:
sels.update(mf._get_self_selectables(flags = flags))
sels.update(mf.get_app_selectables(flags = flags))
sels.update(mf._get_self_selectables(flags=flags))
sels.update(mf.get_app_selectables(flags=flags))
return sels
def get_selectable_names(self, flags = []) -> List[str]:
def get_selectable_names(self, flags=[]) -> List[str]:
"""Return a dict of {'identifier': File} that is selectable from the current file.
Args:
@ -176,18 +177,18 @@ class CardFile(object):
sel_keys.sort()
return sel_keys
def decode_select_response(self, data_hex:str):
def decode_select_response(self, data_hex: str):
"""Decode the response to a SELECT command.
Args:
data_hex: Hex string of the select response
"""
data_hex: Hex string of the select response
"""
# When the current file does not implement a custom select response decoder,
# we just ask the parent file to decode the select response. If this method
# is not overloaded by the current file we will again ask the parent file.
# This way we recursively travel up the file system tree until we hit a file
# that does implement a concrete decoder.
# When the current file does not implement a custom select response decoder,
# we just ask the parent file to decode the select response. If this method
# is not overloaded by the current file we will again ask the parent file.
# This way we recursively travel up the file system tree until we hit a file
# that does implement a concrete decoder.
if self.parent:
return self.parent.decode_select_response(data_hex)
@ -206,6 +207,7 @@ class CardFile(object):
return self.parent.get_profile()
return None
class CardDF(CardFile):
"""DF (Dedicated File) in the smart card filesystem. Those are basically sub-directories."""
@ -225,7 +227,7 @@ class CardDF(CardFile):
def __str__(self):
return "DF(%s)" % (super().__str__())
def add_file(self, child:CardFile, ignore_existing:bool=False):
def add_file(self, child: CardFile, ignore_existing: bool = False):
"""Add a child (DF/EF) to this DF.
Args:
child: The new DF/EF to be added
@ -233,7 +235,7 @@ class CardDF(CardFile):
"""
if not isinstance(child, CardFile):
raise TypeError("Expected a File instance")
if not is_hex(child.fid, minlen = 4, maxlen = 4):
if not is_hex(child.fid, minlen=4, maxlen=4):
raise ValueError("File name %s is not a valid fid" % (child.fid))
if child.name in CardFile.RESERVED_NAMES:
raise ValueError("File name %s is a reserved name" % (child.name))
@ -242,17 +244,20 @@ class CardDF(CardFile):
if child.fid in self.children:
if ignore_existing:
return
raise ValueError("File with given fid %s already exists in %s" % (child.fid, self))
raise ValueError(
"File with given fid %s already exists in %s" % (child.fid, self))
if self.lookup_file_by_sfid(child.sfid):
raise ValueError("File with given sfid %s already exists in %s" % (child.sfid, self))
raise ValueError(
"File with given sfid %s already exists in %s" % (child.sfid, self))
if self.lookup_file_by_name(child.name):
if ignore_existing:
return
raise ValueError("File with given name %s already exists in %s" % (child.name, self))
raise ValueError(
"File with given name %s already exists in %s" % (child.name, self))
self.children[child.fid] = child
child.parent = self
def add_files(self, children:Iterable[CardFile], ignore_existing:bool=False):
def add_files(self, children: Iterable[CardFile], ignore_existing: bool = False):
"""Add a list of child (DF/EF) to this DF
Args:
@ -262,7 +267,7 @@ class CardDF(CardFile):
for child in children:
self.add_file(child, ignore_existing)
def get_selectables(self, flags = []) -> dict:
def get_selectables(self, flags=[]) -> dict:
"""Return a dict of {'identifier': File} that is selectable from the current DF.
Args:
@ -275,12 +280,12 @@ class CardDF(CardFile):
# global selectables + our children
sels = super().get_selectables(flags)
if flags == [] or 'FIDS' in flags:
sels.update({x.fid: x for x in self.children.values() if x.fid})
sels.update({x.fid: x for x in self.children.values() if x.fid})
if flags == [] or 'FNAMES' in flags:
sels.update({x.name: x for x in self.children.values() if x.name})
sels.update({x.name: x for x in self.children.values() if x.name})
return sels
def lookup_file_by_name(self, name:Optional[str]) -> Optional[CardFile]:
def lookup_file_by_name(self, name: Optional[str]) -> Optional[CardFile]:
"""Find a file with given name within current DF."""
if name == None:
return None
@ -289,7 +294,7 @@ class CardDF(CardFile):
return i
return None
def lookup_file_by_sfid(self, sfid:Optional[str]) -> Optional[CardFile]:
def lookup_file_by_sfid(self, sfid: Optional[str]) -> Optional[CardFile]:
"""Find a file with given short file ID within current DF."""
if sfid == None:
return None
@ -298,7 +303,7 @@ class CardDF(CardFile):
return i
return None
def lookup_file_by_fid(self, fid:str) -> Optional[CardFile]:
def lookup_file_by_fid(self, fid: str) -> Optional[CardFile]:
"""Find a file with given file ID within current DF."""
if fid in self.children:
return self.children[fid]
@ -307,6 +312,7 @@ class CardDF(CardFile):
class CardMF(CardDF):
"""MF (Master File) in the smart card filesystem"""
def __init__(self, **kwargs):
# can be overridden; use setdefault
kwargs.setdefault('fid', '3f00')
@ -320,20 +326,20 @@ class CardMF(CardDF):
def __str__(self):
return "MF(%s)" % (self.fid)
def add_application_df(self, app:'CardADF'):
def add_application_df(self, app: 'CardADF'):
"""Add an Application to the MF"""
if not isinstance(app, CardADF):
raise TypeError("Expected an ADF instance")
if app.aid in self.applications:
raise ValueError("AID %s already exists" % (app.aid))
self.applications[app.aid] = app
app.parent=self
app.parent = self
def get_app_names(self):
"""Get list of completions (AID names)"""
return [x.name for x in self.applications]
def get_selectables(self, flags = []) -> dict:
def get_selectables(self, flags=[]) -> dict:
"""Return a dict of {'identifier': File} that is selectable from the current DF.
Args:
@ -347,22 +353,23 @@ class CardMF(CardDF):
sels.update(self.get_app_selectables(flags))
return sels
def get_app_selectables(self, flags = []) -> dict:
def get_app_selectables(self, flags=[]) -> dict:
"""Get applications by AID + name"""
sels = {}
if flags == [] or 'AIDS' in flags:
sels.update({x.aid: x for x in self.applications.values()})
sels.update({x.aid: x for x in self.applications.values()})
if flags == [] or 'ANAMES' in flags:
sels.update({x.name: x for x in self.applications.values() if x.name})
sels.update(
{x.name: x for x in self.applications.values() if x.name})
return sels
def decode_select_response(self, data_hex:str) -> object:
def decode_select_response(self, data_hex: str) -> object:
"""Decode the response to a SELECT command.
This is the fall-back method which automatically defers to the standard decoding
method defined by the card profile. When no profile is set, then no decoding is
performed. Specific derived classes (usually ADF) can overload this method to
install specific decoding.
performed. Specific derived classes (usually ADF) can overload this method to
install specific decoding.
"""
profile = self.get_profile()
@ -372,9 +379,11 @@ class CardMF(CardDF):
else:
return data_hex
class CardADF(CardDF):
"""ADF (Application Dedicated File) in the smart card filesystem"""
def __init__(self, aid:str, **kwargs):
def __init__(self, aid: str, **kwargs):
super().__init__(**kwargs)
# reference to CardApplication may be set from CardApplication constructor
self.application = None # type: Optional[CardApplication]
@ -386,7 +395,7 @@ class CardADF(CardDF):
def __str__(self):
return "ADF(%s)" % (self.aid)
def _path_element(self, prefer_name:bool):
def _path_element(self, prefer_name: bool):
if self.name and prefer_name:
return self.name
else:
@ -395,6 +404,7 @@ class CardADF(CardDF):
class CardEF(CardFile):
"""EF (Entry File) in the smart card filesystem"""
def __init__(self, *, fid, **kwargs):
kwargs['fid'] = fid
super().__init__(**kwargs)
@ -402,7 +412,7 @@ class CardEF(CardFile):
def __str__(self):
return "EF(%s)" % (super().__str__())
def get_selectables(self, flags = []) -> dict:
def get_selectables(self, flags=[]) -> dict:
"""Return a dict of {'identifier': File} that is selectable from the current DF.
Args:
@ -412,9 +422,10 @@ class CardEF(CardFile):
dict containing all selectable items. Key is identifier (string), value
a reference to a CardFile (or derived class) instance.
"""
#global selectable names + those of the parent DF
# global selectable names + those of the parent DF
sels = super().get_selectables(flags)
sels.update({x.name:x for x in self.parent.children.values() if x != self})
sels.update(
{x.name: x for x in self.parent.children.values() if x != self})
return sels
@ -427,12 +438,16 @@ class TransparentEF(CardEF):
@with_default_category('Transparent EF Commands')
class ShellCommands(CommandSet):
"""Shell commands specific for transparent EFs."""
def __init__(self):
super().__init__()
read_bin_parser = argparse.ArgumentParser()
read_bin_parser.add_argument('--offset', type=int, default=0, help='Byte offset for start of read')
read_bin_parser.add_argument('--length', type=int, help='Number of bytes to read')
read_bin_parser.add_argument(
'--offset', type=int, default=0, help='Byte offset for start of read')
read_bin_parser.add_argument(
'--length', type=int, help='Number of bytes to read')
@cmd2.with_argparser(read_bin_parser)
def do_read_binary(self, opts):
"""Read binary data from a transparent EF"""
@ -442,6 +457,7 @@ class TransparentEF(CardEF):
read_bin_dec_parser = argparse.ArgumentParser()
read_bin_dec_parser.add_argument('--oneline', action='store_true',
help='No JSON pretty-printing, dump as a single line')
@cmd2.with_argparser(read_bin_dec_parser)
def do_read_binary_decoded(self, opts):
"""Read + decode data from a transparent EF"""
@ -449,8 +465,11 @@ class TransparentEF(CardEF):
self._cmd.poutput_json(data, opts.oneline)
upd_bin_parser = argparse.ArgumentParser()
upd_bin_parser.add_argument('--offset', type=int, default=0, help='Byte offset for start of read')
upd_bin_parser.add_argument('data', help='Data bytes (hex format) to write')
upd_bin_parser.add_argument(
'--offset', type=int, default=0, help='Byte offset for start of read')
upd_bin_parser.add_argument(
'data', help='Data bytes (hex format) to write')
@cmd2.with_argparser(upd_bin_parser)
def do_update_binary(self, opts):
"""Update (Write) data of a transparent EF"""
@ -459,15 +478,18 @@ class TransparentEF(CardEF):
self._cmd.poutput(data)
upd_bin_dec_parser = argparse.ArgumentParser()
upd_bin_dec_parser.add_argument('data', help='Abstract data (JSON format) to write')
upd_bin_dec_parser.add_argument(
'data', help='Abstract data (JSON format) to write')
upd_bin_dec_parser.add_argument('--json-path', type=str,
help='JSON path to modify specific element of file only')
@cmd2.with_argparser(upd_bin_dec_parser)
def do_update_binary_decoded(self, opts):
"""Encode + Update (Write) data of a transparent EF"""
if opts.json_path:
(data_json, sw) = self._cmd.rs.read_binary_dec()
js_path_modify(data_json, opts.json_path, json.loads(opts.data))
js_path_modify(data_json, opts.json_path,
json.loads(opts.data))
else:
data_json = json.loads(opts.data)
(data, sw) = self._cmd.rs.update_binary_dec(data_json)
@ -493,9 +515,8 @@ class TransparentEF(CardEF):
if data:
self._cmd.poutput_json(data)
def __init__(self, fid:str, sfid:str=None, name:str=None, desc:str=None, parent:CardDF=None,
size={1,None}):
def __init__(self, fid: str, sfid: str = None, name: str = None, desc: str = None, parent: CardDF = None,
size={1, None}):
"""
Args:
fid : File Identifier (4 hex digits)
@ -511,7 +532,7 @@ class TransparentEF(CardEF):
self.size = size
self.shell_commands = [self.ShellCommands()]
def decode_bin(self, raw_bin_data:bytearray) -> dict:
def decode_bin(self, raw_bin_data: bytearray) -> dict:
"""Decode raw (binary) data into abstract representation.
A derived class would typically provide a _decode_bin() or _decode_hex() method
@ -537,7 +558,7 @@ class TransparentEF(CardEF):
return t.to_dict()
return {'raw': raw_bin_data.hex()}
def decode_hex(self, raw_hex_data:str) -> dict:
def decode_hex(self, raw_hex_data: str) -> dict:
"""Decode raw (hex string) data into abstract representation.
A derived class would typically provide a _decode_bin() or _decode_hex() method
@ -564,7 +585,7 @@ class TransparentEF(CardEF):
return t.to_dict()
return {'raw': raw_bin_data.hex()}
def encode_bin(self, abstract_data:dict) -> bytearray:
def encode_bin(self, abstract_data: dict) -> bytearray:
"""Encode abstract representation into raw (binary) data.
A derived class would typically provide an _encode_bin() or _encode_hex() method
@ -588,9 +609,10 @@ class TransparentEF(CardEF):
t = self._tlv() if inspect.isclass(self._tlv) else self._tlv
t.from_dict(abstract_data)
return t.to_tlv()
raise NotImplementedError("%s encoder not yet implemented. Patches welcome." % self)
raise NotImplementedError(
"%s encoder not yet implemented. Patches welcome." % self)
def encode_hex(self, abstract_data:dict) -> str:
def encode_hex(self, abstract_data: dict) -> str:
"""Encode abstract representation into raw (hex string) data.
A derived class would typically provide an _encode_bin() or _encode_hex() method
@ -615,7 +637,8 @@ class TransparentEF(CardEF):
t = self._tlv() if inspect.isclass(self._tlv) else self._tlv
t.from_dict(abstract_data)
return b2h(t.to_tlv())
raise NotImplementedError("%s encoder not yet implemented. Patches welcome." % self)
raise NotImplementedError(
"%s encoder not yet implemented. Patches welcome." % self)
class LinFixedEF(CardEF):
@ -627,12 +650,16 @@ class LinFixedEF(CardEF):
@with_default_category('Linear Fixed EF Commands')
class ShellCommands(CommandSet):
"""Shell commands specific for Linear Fixed EFs."""
def __init__(self):
super().__init__()
read_rec_parser = argparse.ArgumentParser()
read_rec_parser.add_argument('record_nr', type=int, help='Number of record to be read')
read_rec_parser.add_argument('--count', type=int, default=1, help='Number of records to be read, beginning at record_nr')
read_rec_parser.add_argument(
'record_nr', type=int, help='Number of record to be read')
read_rec_parser.add_argument(
'--count', type=int, default=1, help='Number of records to be read, beginning at record_nr')
@cmd2.with_argparser(read_rec_parser)
def do_read_record(self, opts):
"""Read one or multiple records from a record-oriented EF"""
@ -640,15 +667,17 @@ class LinFixedEF(CardEF):
recnr = opts.record_nr + r
(data, sw) = self._cmd.rs.read_record(recnr)
if (len(data) > 0):
recstr = str(data)
recstr = str(data)
else:
recstr = "(empty)"
recstr = "(empty)"
self._cmd.poutput("%03d %s" % (recnr, recstr))
read_rec_dec_parser = argparse.ArgumentParser()
read_rec_dec_parser.add_argument('record_nr', type=int, help='Number of record to be read')
read_rec_dec_parser.add_argument(
'record_nr', type=int, help='Number of record to be read')
read_rec_dec_parser.add_argument('--oneline', action='store_true',
help='No JSON pretty-printing, dump as a single line')
@cmd2.with_argparser(read_rec_dec_parser)
def do_read_record_decoded(self, opts):
"""Read + decode a record from a record-oriented EF"""
@ -656,6 +685,7 @@ class LinFixedEF(CardEF):
self._cmd.poutput_json(data, opts.oneline)
read_recs_parser = argparse.ArgumentParser()
@cmd2.with_argparser(read_recs_parser)
def do_read_records(self, opts):
"""Read all records from a record-oriented EF"""
@ -663,14 +693,15 @@ class LinFixedEF(CardEF):
for recnr in range(1, 1 + num_of_rec):
(data, sw) = self._cmd.rs.read_record(recnr)
if (len(data) > 0):
recstr = str(data)
recstr = str(data)
else:
recstr = "(empty)"
recstr = "(empty)"
self._cmd.poutput("%03d %s" % (recnr, recstr))
read_recs_dec_parser = argparse.ArgumentParser()
read_recs_dec_parser.add_argument('--oneline', action='store_true',
help='No JSON pretty-printing, dump as a single line')
help='No JSON pretty-printing, dump as a single line')
@cmd2.with_argparser(read_recs_dec_parser)
def do_read_records_decoded(self, opts):
"""Read + decode all records from a record-oriented EF"""
@ -683,8 +714,11 @@ class LinFixedEF(CardEF):
self._cmd.poutput_json(data_list, opts.oneline)
upd_rec_parser = argparse.ArgumentParser()
upd_rec_parser.add_argument('record_nr', type=int, help='Number of record to be read')
upd_rec_parser.add_argument('data', help='Data bytes (hex format) to write')
upd_rec_parser.add_argument(
'record_nr', type=int, help='Number of record to be read')
upd_rec_parser.add_argument(
'data', help='Data bytes (hex format) to write')
@cmd2.with_argparser(upd_rec_parser)
def do_update_record(self, opts):
"""Update (write) data to a record-oriented EF"""
@ -693,24 +727,31 @@ class LinFixedEF(CardEF):
self._cmd.poutput(data)
upd_rec_dec_parser = argparse.ArgumentParser()
upd_rec_dec_parser.add_argument('record_nr', type=int, help='Number of record to be read')
upd_rec_dec_parser.add_argument('data', help='Abstract data (JSON format) to write')
upd_rec_dec_parser.add_argument(
'record_nr', type=int, help='Number of record to be read')
upd_rec_dec_parser.add_argument(
'data', help='Abstract data (JSON format) to write')
upd_rec_dec_parser.add_argument('--json-path', type=str,
help='JSON path to modify specific element of record only')
@cmd2.with_argparser(upd_rec_dec_parser)
def do_update_record_decoded(self, opts):
"""Encode + Update (write) data to a record-oriented EF"""
if opts.json_path:
(data_json, sw) = self._cmd.rs.read_record_dec(opts.record_nr)
js_path_modify(data_json, opts.json_path, json.loads(opts.data))
js_path_modify(data_json, opts.json_path,
json.loads(opts.data))
else:
data_json = json.loads(opts.data)
(data, sw) = self._cmd.rs.update_record_dec(opts.record_nr, data_json)
(data, sw) = self._cmd.rs.update_record_dec(
opts.record_nr, data_json)
if data:
self._cmd.poutput(data)
edit_rec_dec_parser = argparse.ArgumentParser()
edit_rec_dec_parser.add_argument('record_nr', type=int, help='Number of record to be edited')
edit_rec_dec_parser.add_argument(
'record_nr', type=int, help='Number of record to be edited')
@cmd2.with_argparser(edit_rec_dec_parser)
def do_edit_record_decoded(self, opts):
"""Edit the JSON representation of one record in an editor."""
@ -727,13 +768,13 @@ class LinFixedEF(CardEF):
if edited_json == orig_json:
self._cmd.poutput("Data not modified, skipping write")
else:
(data, sw) = self._cmd.rs.update_record_dec(opts.record_nr, edited_json)
(data, sw) = self._cmd.rs.update_record_dec(
opts.record_nr, edited_json)
if data:
self._cmd.poutput_json(data)
def __init__(self, fid:str, sfid:str=None, name:str=None, desc:str=None,
parent:Optional[CardDF]=None, rec_len={1,None}):
def __init__(self, fid: str, sfid: str = None, name: str = None, desc: str = None,
parent: Optional[CardDF] = None, rec_len={1, None}):
"""
Args:
fid : File Identifier (4 hex digits)
@ -749,7 +790,7 @@ class LinFixedEF(CardEF):
self._construct = None
self._tlv = None
def decode_record_hex(self, raw_hex_data:str) -> dict:
def decode_record_hex(self, raw_hex_data: str) -> dict:
"""Decode raw (hex string) data into abstract representation.
A derived class would typically provide a _decode_record_bin() or _decode_record_hex()
@ -776,7 +817,7 @@ class LinFixedEF(CardEF):
return t.to_dict()
return {'raw': raw_bin_data.hex()}
def decode_record_bin(self, raw_bin_data:bytearray) -> dict:
def decode_record_bin(self, raw_bin_data: bytearray) -> dict:
"""Decode raw (binary) data into abstract representation.
A derived class would typically provide a _decode_record_bin() or _decode_record_hex()
@ -803,7 +844,7 @@ class LinFixedEF(CardEF):
return t.to_dict()
return {'raw': raw_hex_data}
def encode_record_hex(self, abstract_data:dict) -> str:
def encode_record_hex(self, abstract_data: dict) -> str:
"""Encode abstract representation into raw (hex string) data.
A derived class would typically provide an _encode_record_bin() or _encode_record_hex()
@ -828,9 +869,10 @@ class LinFixedEF(CardEF):
t = self._tlv() if inspect.isclass(self._tlv) else self._tlv
t.from_dict(abstract_data)
return b2h(t.to_tlv())
raise NotImplementedError("%s encoder not yet implemented. Patches welcome." % self)
raise NotImplementedError(
"%s encoder not yet implemented. Patches welcome." % self)
def encode_record_bin(self, abstract_data:dict) -> bytearray:
def encode_record_bin(self, abstract_data: dict) -> bytearray:
"""Encode abstract representation into raw (binary) data.
A derived class would typically provide an _encode_record_bin() or _encode_record_hex()
@ -854,14 +896,19 @@ class LinFixedEF(CardEF):
t = self._tlv() if inspect.isclass(self._tlv) else self._tlv
t.from_dict(abstract_data)
return t.to_tlv()
raise NotImplementedError("%s encoder not yet implemented. Patches welcome." % self)
raise NotImplementedError(
"%s encoder not yet implemented. Patches welcome." % self)
class CyclicEF(LinFixedEF):
"""Cyclic EF (Entry File) in the smart card filesystem"""
# we don't really have any special support for those; just recycling LinFixedEF here
def __init__(self, fid:str, sfid:str=None, name:str=None, desc:str=None, parent:CardDF=None,
rec_len={1,None}):
super().__init__(fid=fid, sfid=sfid, name=name, desc=desc, parent=parent, rec_len=rec_len)
def __init__(self, fid: str, sfid: str = None, name: str = None, desc: str = None, parent: CardDF = None,
rec_len={1, None}):
super().__init__(fid=fid, sfid=sfid, name=name,
desc=desc, parent=parent, rec_len=rec_len)
class TransRecEF(TransparentEF):
"""Transparent EF (Entry File) containing fixed-size records.
@ -872,8 +919,9 @@ class TransRecEF(TransparentEF):
We add a special class for those, so the user only has to provide encoder/decoder functions
for a record, while this class takes care of split / merge of records.
"""
def __init__(self, fid:str, rec_len:int, sfid:str=None, name:str=None, desc:str=None,
parent:Optional[CardDF]=None, size={1,None}):
def __init__(self, fid: str, rec_len: int, sfid: str = None, name: str = None, desc: str = None,
parent: Optional[CardDF] = None, size={1, None}):
"""
Args:
fid : File Identifier (4 hex digits)
@ -887,7 +935,7 @@ class TransRecEF(TransparentEF):
super().__init__(fid=fid, sfid=sfid, name=name, desc=desc, parent=parent, size=size)
self.rec_len = rec_len
def decode_record_hex(self, raw_hex_data:str) -> dict:
def decode_record_hex(self, raw_hex_data: str) -> dict:
"""Decode raw (hex string) data into abstract representation.
A derived class would typically provide a _decode_record_bin() or _decode_record_hex()
@ -914,7 +962,7 @@ class TransRecEF(TransparentEF):
return t.to_dict()
return {'raw': raw_hex_data}
def decode_record_bin(self, raw_bin_data:bytearray) -> dict:
def decode_record_bin(self, raw_bin_data: bytearray) -> dict:
"""Decode raw (binary) data into abstract representation.
A derived class would typically provide a _decode_record_bin() or _decode_record_hex()
@ -941,7 +989,7 @@ class TransRecEF(TransparentEF):
return t.to_dict()
return {'raw': raw_hex_data}
def encode_record_hex(self, abstract_data:dict) -> str:
def encode_record_hex(self, abstract_data: dict) -> str:
"""Encode abstract representation into raw (hex string) data.
A derived class would typically provide an _encode_record_bin() or _encode_record_hex()
@ -965,9 +1013,10 @@ class TransRecEF(TransparentEF):
t = self._tlv() if inspect.isclass(self._tlv) else self._tlv
t.from_dict(abstract_data)
return b2h(t.to_tlv())
raise NotImplementedError("%s encoder not yet implemented. Patches welcome." % self)
raise NotImplementedError(
"%s encoder not yet implemented. Patches welcome." % self)
def encode_record_bin(self, abstract_data:dict) -> bytearray:
def encode_record_bin(self, abstract_data: dict) -> bytearray:
"""Encode abstract representation into raw (binary) data.
A derived class would typically provide an _encode_record_bin() or _encode_record_hex()
@ -991,10 +1040,12 @@ class TransRecEF(TransparentEF):
t = self._tlv() if inspect.isclass(self._tlv) else self._tlv
t.from_dict(abstract_data)
return t.to_tlv()
raise NotImplementedError("%s encoder not yet implemented. Patches welcome." % self)
raise NotImplementedError(
"%s encoder not yet implemented. Patches welcome." % self)
def _decode_bin(self, raw_bin_data:bytearray):
chunks = [raw_bin_data[i:i+self.rec_len] for i in range(0, len(raw_bin_data), self.rec_len)]
def _decode_bin(self, raw_bin_data: bytearray):
chunks = [raw_bin_data[i:i+self.rec_len]
for i in range(0, len(raw_bin_data), self.rec_len)]
return [self.decode_record_bin(x) for x in chunks]
def _encode_bin(self, abstract_data) -> bytes:
@ -1014,11 +1065,14 @@ class BerTlvEF(CardEF):
@with_default_category('BER-TLV EF Commands')
class ShellCommands(CommandSet):
"""Shell commands specific for BER-TLV EFs."""
def __init__(self):
super().__init__()
retrieve_data_parser = argparse.ArgumentParser()
retrieve_data_parser.add_argument('tag', type=auto_int, help='BER-TLV Tag of value to retrieve')
retrieve_data_parser.add_argument(
'tag', type=auto_int, help='BER-TLV Tag of value to retrieve')
@cmd2.with_argparser(retrieve_data_parser)
def do_retrieve_data(self, opts):
"""Retrieve (Read) data from a BER-TLV EF"""
@ -1031,8 +1085,11 @@ class BerTlvEF(CardEF):
self._cmd.poutput(tags)
set_data_parser = argparse.ArgumentParser()
set_data_parser.add_argument('tag', type=auto_int, help='BER-TLV Tag of value to set')
set_data_parser.add_argument('data', help='Data bytes (hex format) to write')
set_data_parser.add_argument(
'tag', type=auto_int, help='BER-TLV Tag of value to set')
set_data_parser.add_argument(
'data', help='Data bytes (hex format) to write')
@cmd2.with_argparser(set_data_parser)
def do_set_data(self, opts):
"""Set (Write) data for a given tag in a BER-TLV EF"""
@ -1041,7 +1098,9 @@ class BerTlvEF(CardEF):
self._cmd.poutput(data)
del_data_parser = argparse.ArgumentParser()
del_data_parser.add_argument('tag', type=auto_int, help='BER-TLV Tag of value to set')
del_data_parser.add_argument(
'tag', type=auto_int, help='BER-TLV Tag of value to set')
@cmd2.with_argparser(del_data_parser)
def do_delete_data(self, opts):
"""Delete data for a given tag in a BER-TLV EF"""
@ -1049,9 +1108,8 @@ class BerTlvEF(CardEF):
if data:
self._cmd.poutput(data)
def __init__(self, fid:str, sfid:str=None, name:str=None, desc:str=None, parent:CardDF=None,
size={1,None}):
def __init__(self, fid: str, sfid: str = None, name: str = None, desc: str = None, parent: CardDF = None,
size={1, None}):
"""
Args:
fid : File Identifier (4 hex digits)
@ -1069,7 +1127,8 @@ class BerTlvEF(CardEF):
class RuntimeState(object):
"""Represent the runtime state of a session with a card."""
def __init__(self, card, profile:'CardProfile'):
def __init__(self, card, profile: 'CardProfile'):
"""
Args:
card : pysim.cards.Card instance
@ -1077,12 +1136,13 @@ class RuntimeState(object):
"""
self.mf = CardMF(profile=profile)
self.card = card
self.selected_file = self.mf # type: CardDF
self.selected_file = self.mf # type: CardDF
self.profile = profile
# make sure the class and selection control bytes, which are specified
# by the card profile are used
self.card.set_apdu_parameter(cla=self.profile.cla, sel_ctrl=self.profile.sel_ctrl)
self.card.set_apdu_parameter(
cla=self.profile.cla, sel_ctrl=self.profile.sel_ctrl)
# add application ADFs + MF-files from profile
apps = self._match_applications()
@ -1170,7 +1230,7 @@ class RuntimeState(object):
node = node.parent
return None
def interpret_sw(self, sw:str):
def interpret_sw(self, sw: str):
"""Interpret a given status word relative to the currently selected application
or the underlying card profile.
@ -1191,11 +1251,12 @@ class RuntimeState(object):
res = app.interpret_sw(sw)
return res or self.profile.interpret_sw(sw)
def probe_file(self, fid:str, cmd_app=None):
def probe_file(self, fid: str, cmd_app=None):
"""Blindly try to select a file and automatically add a matching file
object if the file actually exists."""
object if the file actually exists."""
if not is_hex(fid, 4, 4):
raise ValueError("Cannot select unknown file by name %s, only hexadecimal 4 digit FID is allowed" % fid)
raise ValueError(
"Cannot select unknown file by name %s, only hexadecimal 4 digit FID is allowed" % fid)
try:
(data, sw) = self.card._scc.select_file(fid)
@ -1207,18 +1268,21 @@ class RuntimeState(object):
select_resp = self.selected_file.decode_select_response(data)
if (select_resp['file_descriptor']['file_type'] == 'df'):
f = CardDF(fid=fid, sfid=None, name="DF." + str(fid).upper(), desc="dedicated file, manually added at runtime")
f = CardDF(fid=fid, sfid=None, name="DF." + str(fid).upper(),
desc="dedicated file, manually added at runtime")
else:
if (select_resp['file_descriptor']['structure'] == 'transparent'):
f = TransparentEF(fid=fid, sfid=None, name="EF." + str(fid).upper(), desc="elementary file, manually added at runtime")
f = TransparentEF(fid=fid, sfid=None, name="EF." + str(fid).upper(),
desc="elementary file, manually added at runtime")
else:
f = LinFixedEF(fid=fid, sfid=None, name="EF." + str(fid).upper(), desc="elementary file, manually added at runtime")
f = LinFixedEF(fid=fid, sfid=None, name="EF." + str(fid).upper(),
desc="elementary file, manually added at runtime")
self.selected_file.add_files([f])
self.selected_file = f
return select_resp
def select(self, name:str, cmd_app=None):
def select(self, name: str, cmd_app=None):
"""Select a file (EF, DF, ADF, MF, ...).
Args:
@ -1265,14 +1329,14 @@ class RuntimeState(object):
(data, sw) = self.card._scc.status()
return self.selected_file.decode_select_response(data)
def activate_file(self, name:str):
def activate_file(self, name: str):
"""Request ACTIVATE FILE of specified file."""
sels = self.selected_file.get_selectables()
f = sels[name]
data, sw = self.card._scc.activate_file(f.fid)
return data, sw
def read_binary(self, length:int=None, offset:int=0):
def read_binary(self, length: int = None, offset: int = 0):
"""Read [part of] a transparent EF binary data.
Args:
@ -1298,7 +1362,7 @@ class RuntimeState(object):
dec_data = self.selected_file.decode_hex(data)
return (dec_data, sw)
def update_binary(self, data_hex:str, offset:int=0):
def update_binary(self, data_hex: str, offset: int = 0):
"""Update transparent EF binary data.
Args:
@ -1309,7 +1373,7 @@ class RuntimeState(object):
raise TypeError("Only works with TransparentEF")
return self.card._scc.update_binary(self.selected_file.fid, data_hex, offset, conserve=self.conserve_write)
def update_binary_dec(self, data:dict):
def update_binary_dec(self, data: dict):
"""Update transparent EF from abstract data. Encodes the data to binary and
then updates the EF with it.
@ -1319,7 +1383,7 @@ class RuntimeState(object):
data_hex = self.selected_file.encode_hex(data)
return self.update_binary(data_hex)
def read_record(self, rec_nr:int=0):
def read_record(self, rec_nr: int = 0):
"""Read a record as binary data.
Args:
@ -1332,7 +1396,7 @@ class RuntimeState(object):
# returns a string of hex nibbles
return self.card._scc.read_record(self.selected_file.fid, rec_nr)
def read_record_dec(self, rec_nr:int=0) -> Tuple[dict, str]:
def read_record_dec(self, rec_nr: int = 0) -> Tuple[dict, str]:
"""Read a record and decode it to abstract data.
Args:
@ -1343,7 +1407,7 @@ class RuntimeState(object):
(data, sw) = self.read_record(rec_nr)
return (self.selected_file.decode_record_hex(data), sw)
def update_record(self, rec_nr:int, data_hex:str):
def update_record(self, rec_nr: int, data_hex: str):
"""Update a record with given binary data
Args:
@ -1354,7 +1418,7 @@ class RuntimeState(object):
raise TypeError("Only works with Linear Fixed EF")
return self.card._scc.update_record(self.selected_file.fid, rec_nr, data_hex, conserve=self.conserve_write)
def update_record_dec(self, rec_nr:int, data:dict):
def update_record_dec(self, rec_nr: int, data: dict):
"""Update a record with given abstract data. Will encode abstract to binary data
and then write it to the given record on the card.
@ -1365,7 +1429,7 @@ class RuntimeState(object):
data_hex = self.selected_file.encode_record_hex(data)
return self.update_record(rec_nr, data_hex)
def retrieve_data(self, tag:int=0):
def retrieve_data(self, tag: int = 0):
"""Read a DO/TLV as binary data.
Args:
@ -1390,7 +1454,7 @@ class RuntimeState(object):
tag, length, value, remainder = bertlv_parse_one(h2b(data))
return list(value)
def set_data(self, tag:int, data_hex:str):
def set_data(self, tag: int, data_hex: str):
"""Update a TLV/DO with given binary data
Args:
@ -1408,15 +1472,15 @@ class RuntimeState(object):
cmd_app.unregister_command_set(c)
class FileData(object):
"""Represent the runtime, on-card data."""
def __init__(self, fdesc):
self.desc = fdesc
self.fcp = None
def interpret_sw(sw_data:dict, sw:str):
def interpret_sw(sw_data: dict, sw: str):
"""Interpret a given status word.
Args:
@ -1435,10 +1499,12 @@ def interpret_sw(sw_data:dict, sw:str):
return (class_str, descr)
return None
class CardApplication(object):
"""A card application is represented by an ADF (with contained hierarchy) and optionally
some SW definitions."""
def __init__(self, name, adf:Optional[CardADF]=None, aid:str=None, sw:dict=None):
def __init__(self, name, adf: Optional[CardADF] = None, aid: str = None, sw: dict = None):
"""
Args:
adf : ADF name
@ -1477,11 +1543,11 @@ class CardModel(abc.ABC):
@classmethod
@abc.abstractmethod
def add_files(cls, rs:RuntimeState):
def add_files(cls, rs: RuntimeState):
"""Add model specific files to given RuntimeState."""
@classmethod
def match(cls, scc:SimCardCommands) -> bool:
def match(cls, scc: SimCardCommands) -> bool:
"""Test if given card matches this model."""
card_atr = scc.get_atr()
for atr in cls._atrs:
@ -1492,7 +1558,7 @@ class CardModel(abc.ABC):
return False
@staticmethod
def apply_matching_models(scc:SimCardCommands, rs:RuntimeState):
def apply_matching_models(scc: SimCardCommands, rs: RuntimeState):
"""Check if any of the CardModel sub-classes 'match' the currently inserted card
(by ATR or overriding the 'match' method). If so, call their 'add_files'
method."""

View File

@ -43,26 +43,32 @@ import pySim.ts_51_011
# DF.EIRENE (FFFIS for GSM-R SIM Cards)
######################################################################
class FuncNTypeAdapter(Adapter):
def _decode(self, obj, context, path):
bcd = swap_nibbles(b2h(obj))
last_digit = bcd[-1]
return {'functional_number': bcd[:-1],
'presentation_of_only_this_fn': last_digit & 4,
'permanent_fn': last_digit & 8 }
'permanent_fn': last_digit & 8}
def _encode(self, obj, context, path):
return 'FIXME'
class EF_FN(LinFixedEF):
"""Section 7.2"""
def __init__(self):
super().__init__(fid='6ff1', sfid=None, name='EF.EN', desc='Functional numbers', rec_len={9,9})
super().__init__(fid='6ff1', sfid=None, name='EF.EN',
desc='Functional numbers', rec_len={9, 9})
self._construct = Struct('functional_number_and_type'/FuncNTypeAdapter(Bytes(8)),
'list_number'/Int8ub)
class PlConfAdapter(Adapter):
"""Section 7.4.3"""
def _decode(self, obj, context, path):
num = int(obj) & 0x7
if num == 0:
@ -77,6 +83,7 @@ class PlConfAdapter(Adapter):
return 1
elif num == 5:
return 0
def _encode(self, obj, context, path):
if obj == 'None':
return 0
@ -92,8 +99,10 @@ class PlConfAdapter(Adapter):
elif obj == 0:
return 5
class PlCallAdapter(Adapter):
"""Section 7.4.12"""
def _decode(self, obj, context, path):
num = int(obj) & 0x7
if num == 0:
@ -112,6 +121,7 @@ class PlCallAdapter(Adapter):
return 'B'
elif num == 7:
return 'A'
def _encode(self, obj, context, path):
if obj == 'None':
return 0
@ -130,12 +140,16 @@ class PlCallAdapter(Adapter):
elif obj == 'A':
return 7
NextTableType = Enum(Byte, decision=0xf0, predefined=0xf1, num_dial_digits=0xf2, ic=0xf3, empty=0xff)
NextTableType = Enum(Byte, decision=0xf0, predefined=0xf1,
num_dial_digits=0xf2, ic=0xf3, empty=0xff)
class EF_CallconfC(TransparentEF):
"""Section 7.3"""
def __init__(self):
super().__init__(fid='6ff2', sfid=None, name='EF.CallconfC', size={24,24},
super().__init__(fid='6ff2', sfid=None, name='EF.CallconfC', size={24, 24},
desc='Call Configuration of emergency calls Configuration')
self._construct = Struct('pl_conf'/PlConfAdapter(Int8ub),
'conf_nr'/BcdAdapter(Bytes(8)),
@ -147,29 +161,39 @@ class EF_CallconfC(TransparentEF):
'shunting_emergency_gid'/Int8ub,
'imei'/BcdAdapter(Bytes(8)))
class EF_CallconfI(LinFixedEF):
"""Section 7.5"""
def __init__(self):
super().__init__(fid='6ff3', sfid=None, name='EF.CallconfI', rec_len={21,21},
super().__init__(fid='6ff3', sfid=None, name='EF.CallconfI', rec_len={21, 21},
desc='Call Configuration of emergency calls Information')
self._construct = Struct('t_dur'/Int24ub,
't_relcalc'/Int32ub,
'pl_call'/PlCallAdapter(Int8ub),
'cause'/FlagsEnum(Int8ub, powered_off=1, radio_link_error=2, user_command=5),
'cause' /
FlagsEnum(Int8ub, powered_off=1,
radio_link_error=2, user_command=5),
'gcr'/BcdAdapter(Bytes(4)),
'fnr'/BcdAdapter(Bytes(8)))
class EF_Shunting(TransparentEF):
"""Section 7.6"""
def __init__(self):
super().__init__(fid='6ff4', sfid=None, name='EF.Shunting', desc='Shunting', size={8,8})
super().__init__(fid='6ff4', sfid=None,
name='EF.Shunting', desc='Shunting', size={8, 8})
self._construct = Struct('common_gid'/Int8ub,
'shunting_gid'/Bytes(7))
class EF_GsmrPLMN(LinFixedEF):
"""Section 7.7"""
def __init__(self):
super().__init__(fid='6ff5', sfid=None, name='EF.GsmrPLMN', desc='GSM-R network selection', rec_len={9,9})
super().__init__(fid='6ff5', sfid=None, name='EF.GsmrPLMN',
desc='GSM-R network selection', rec_len={9, 9})
self._construct = Struct('plmn'/BcdAdapter(Bytes(3)),
'class_of_network'/BitStruct('supported'/FlagsEnum(BitsInteger(5), vbs=1, vgcs=2, emlpp=4, fn=8, eirene=16),
'preference'/BitsInteger(3)),
@ -177,34 +201,46 @@ class EF_GsmrPLMN(LinFixedEF):
'outgoing_ref_tbl'/HexAdapter(Bytes(2)),
'ic_table_ref'/HexAdapter(Bytes(1)))
class EF_IC(LinFixedEF):
"""Section 7.8"""
def __init__(self):
super().__init__(fid='6f8d', sfid=None, name='EF.IC', desc='International Code', rec_len={7,7})
super().__init__(fid='6f8d', sfid=None, name='EF.IC',
desc='International Code', rec_len={7, 7})
self._construct = Struct('next_table_type'/NextTableType,
'id_of_next_table'/HexAdapter(Bytes(2)),
'ic_decision_value'/BcdAdapter(Bytes(2)),
'network_string_table_index'/Int8ub)
class EF_NW(LinFixedEF):
"""Section 7.9"""
def __init__(self):
super().__init__(fid='6f80', sfid=None, name='EF.NW', desc='Network Name', rec_len={8,8})
super().__init__(fid='6f80', sfid=None, name='EF.NW',
desc='Network Name', rec_len={8, 8})
self._construct = GsmString(8)
class EF_Switching(LinFixedEF):
"""Section 8.4"""
def __init__(self, fid, name, desc):
super().__init__(fid=fid, sfid=None, name=name, desc=desc, rec_len={6,6})
super().__init__(fid=fid, sfid=None,
name=name, desc=desc, rec_len={6, 6})
self._construct = Struct('next_table_type'/NextTableType,
'id_of_next_table'/HexAdapter(Bytes(2)),
'decision_value'/BcdAdapter(Bytes(2)),
'string_table_index'/Int8ub)
class EF_Predefined(LinFixedEF):
"""Section 8.5"""
def __init__(self, fid, name, desc):
super().__init__(fid=fid, sfid=None, name=name, desc=desc, rec_len={3,3})
super().__init__(fid=fid, sfid=None,
name=name, desc=desc, rec_len={3, 3})
# header and other records have different structure. WTF !?!
self._construct = Struct('next_table_type'/NextTableType,
'id_of_next_table'/HexAdapter(Bytes(2)),
@ -212,10 +248,12 @@ class EF_Predefined(LinFixedEF):
'string_table_index1'/Int8ub)
# TODO: predefined value n, ...
class EF_DialledVals(TransparentEF):
"""Section 8.6"""
def __init__(self, fid, name, desc):
super().__init__(fid=fid, sfid=None, name=name, desc=desc, size={4,4})
super().__init__(fid=fid, sfid=None, name=name, desc=desc, size={4, 4})
self._construct = Struct('next_table_type'/NextTableType,
'id_of_next_table'/HexAdapter(Bytes(2)),
'dialed_digits'/BcdAdapter(Bytes(1)))
@ -238,18 +276,31 @@ class DF_EIRENE(CardDF):
EF_Switching(fid='6f8e', name='EF.CT', desc='Call Type'),
EF_Switching(fid='6f8f', name='EF.SC', desc='Short Code'),
EF_Predefined(fid='6f88', name='EF.FC', desc='Function Code'),
EF_Predefined(fid='6f89', name='EF.Service', desc='VGCS/VBS Service Code'),
EF_Predefined(fid='6f8a', name='EF.Call', desc='First digit of the group ID'),
EF_Predefined(fid='6f8b', name='EF.FctTeam', desc='Call Type 6 Team Type + Team member function'),
EF_Predefined(fid='6f92', name='EF.Controller', desc='Call Type 7 Controller function code'),
EF_Predefined(fid='6f8c', name='EF.Gateway', desc='Access to external networks'),
EF_DialledVals(fid='6f81', name='EF.5to8digits', desc='Call Type 2 User Identity Number length'),
EF_DialledVals(fid='6f82', name='EF.2digits', desc='2 digits input'),
EF_DialledVals(fid='6f83', name='EF.8digits', desc='8 digits input'),
EF_DialledVals(fid='6f84', name='EF.9digits', desc='9 digits input'),
EF_DialledVals(fid='6f85', name='EF.SSSSS', desc='Group call area input'),
EF_DialledVals(fid='6f86', name='EF.LLLLL', desc='Location number Call Type 6'),
EF_DialledVals(fid='6f91', name='EF.Location', desc='Location number Call Type 7'),
EF_DialledVals(fid='6f87', name='EF.FreeNumber', desc='Free Number Call Type 0 and 8'),
]
EF_Predefined(fid='6f89', name='EF.Service',
desc='VGCS/VBS Service Code'),
EF_Predefined(fid='6f8a', name='EF.Call',
desc='First digit of the group ID'),
EF_Predefined(fid='6f8b', name='EF.FctTeam',
desc='Call Type 6 Team Type + Team member function'),
EF_Predefined(fid='6f92', name='EF.Controller',
desc='Call Type 7 Controller function code'),
EF_Predefined(fid='6f8c', name='EF.Gateway',
desc='Access to external networks'),
EF_DialledVals(fid='6f81', name='EF.5to8digits',
desc='Call Type 2 User Identity Number length'),
EF_DialledVals(fid='6f82', name='EF.2digits',
desc='2 digits input'),
EF_DialledVals(fid='6f83', name='EF.8digits',
desc='8 digits input'),
EF_DialledVals(fid='6f84', name='EF.9digits',
desc='9 digits input'),
EF_DialledVals(fid='6f85', name='EF.SSSSS',
desc='Group call area input'),
EF_DialledVals(fid='6f86', name='EF.LLLLL',
desc='Location number Call Type 6'),
EF_DialledVals(fid='6f91', name='EF.Location',
desc='Location number Call Type 7'),
EF_DialledVals(fid='6f87', name='EF.FreeNumber',
desc='Free Number Call Type 0 and 8'),
]
self.add_files(files)

View File

@ -24,39 +24,57 @@ from pySim.filesystem import *
from pySim.tlv import *
# Table 91 + Section 8.2.1.2
class ApplicationId(BER_TLV_IE, tag=0x4f):
_construct = GreedyBytes
# Table 91
class ApplicationLabel(BER_TLV_IE, tag=0x50):
_construct = GreedyBytes
# Table 91 + Section 5.3.1.2
class FileReference(BER_TLV_IE, tag=0x51):
_construct = GreedyBytes
# Table 91
class CommandApdu(BER_TLV_IE, tag=0x52):
_construct = GreedyBytes
# Table 91
class DiscretionaryData(BER_TLV_IE, tag=0x53):
_construct = GreedyBytes
# Table 91
class DiscretionaryTemplate(BER_TLV_IE, tag=0x73):
_construct = GreedyBytes
# Table 91 + RFC1738 / RFC2396
class URL(BER_TLV_IE, tag=0x5f50):
_construct = GreedyString('ascii')
# Table 91
class ApplicationRelatedDOSet(BER_TLV_IE, tag=0x61):
_construct = GreedyBytes
# Section 8.2.1.3 Application Template
class ApplicationTemplate(BER_TLV_IE, tag=0x61, nested=[ApplicationId, ApplicationLabel, FileReference,
CommandApdu, DiscretionaryData, DiscretionaryTemplate,URL,
CommandApdu, DiscretionaryData, DiscretionaryTemplate, URL,
ApplicationRelatedDOSet]):
pass

View File

@ -25,6 +25,7 @@ of a file or record in its JSON representation.
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
def js_path_find(js_dict, js_path):
"""Find/Match a JSON path within a given JSON-serializable dict.
Args:
@ -35,6 +36,7 @@ def js_path_find(js_dict, js_path):
jsonpath_expr = jsonpath_ng.parse(js_path)
return jsonpath_expr.find(js_dict)
def js_path_modify(js_dict, js_path, new_val):
"""Find/Match a JSON path within a given JSON-serializable dict.
Args:
@ -45,4 +47,3 @@ def js_path_modify(js_dict, js_path, new_val):
jsonpath_expr = jsonpath_ng.parse(js_path)
jsonpath_expr.find(js_dict)
jsonpath_expr.update(js_dict, new_val)

View File

@ -27,122 +27,126 @@ from pySim.utils import all_subclasses
import abc
import operator
def _mf_select_test(scc:SimCardCommands, cla_byte:str, sel_ctrl:str) -> bool:
cla_byte_bak = scc.cla_byte
sel_ctrl_bak = scc.sel_ctrl
scc.reset_card()
scc.cla_byte = cla_byte
scc.sel_ctrl = sel_ctrl
rc = True
try:
scc.select_file('3f00')
except:
rc = False
def _mf_select_test(scc: SimCardCommands, cla_byte: str, sel_ctrl: str) -> bool:
cla_byte_bak = scc.cla_byte
sel_ctrl_bak = scc.sel_ctrl
scc.reset_card()
scc.reset_card()
scc.cla_byte = cla_byte_bak
scc.sel_ctrl = sel_ctrl_bak
return rc
scc.cla_byte = cla_byte
scc.sel_ctrl = sel_ctrl
rc = True
try:
scc.select_file('3f00')
except:
rc = False
def match_uicc(scc:SimCardCommands) -> bool:
""" Try to access MF via UICC APDUs (3GPP TS 102.221), if this works, the
card is considered a UICC card.
"""
return _mf_select_test(scc, "00", "0004")
scc.reset_card()
scc.cla_byte = cla_byte_bak
scc.sel_ctrl = sel_ctrl_bak
return rc
def match_uicc(scc: SimCardCommands) -> bool:
""" Try to access MF via UICC APDUs (3GPP TS 102.221), if this works, the
card is considered a UICC card.
"""
return _mf_select_test(scc, "00", "0004")
def match_sim(scc: SimCardCommands) -> bool:
""" Try to access MF via 2G APDUs (3GPP TS 11.11), if this works, the card
is also a simcard. This will be the case for most UICC cards, but there may
also be plain UICC cards without 2G support as well.
"""
return _mf_select_test(scc, "a0", "0000")
def match_sim(scc:SimCardCommands) -> bool:
""" Try to access MF via 2G APDUs (3GPP TS 11.11), if this works, the card
is also a simcard. This will be the case for most UICC cards, but there may
also be plain UICC cards without 2G support as well.
"""
return _mf_select_test(scc, "a0", "0000")
class CardProfile(object):
"""A Card Profile describes a card, it's filesystem hierarchy, an [initial] list of
applications as well as profile-specific SW and shell commands. Every card has
one card profile, but there may be multiple applications within that profile."""
"""A Card Profile describes a card, it's filesystem hierarchy, an [initial] list of
applications as well as profile-specific SW and shell commands. Every card has
one card profile, but there may be multiple applications within that profile."""
def __init__(self, name, **kw):
"""
Args:
desc (str) : Description
files_in_mf : List of CardEF instances present in MF
applications : List of CardApplications present on card
sw : List of status word definitions
shell_cmdsets : List of cmd2 shell command sets of profile-specific commands
cla : class byte that should be used with cards of this profile
sel_ctrl : selection control bytes class byte that should be used with cards of this profile
"""
self.name = name
self.desc = kw.get("desc", None)
self.files_in_mf = kw.get("files_in_mf", [])
self.sw = kw.get("sw", {})
self.applications = kw.get("applications", [])
self.shell_cmdsets = kw.get("shell_cmdsets", [])
self.cla = kw.get("cla", "00")
self.sel_ctrl = kw.get("sel_ctrl", "0004")
def __init__(self, name, **kw):
"""
Args:
desc (str) : Description
files_in_mf : List of CardEF instances present in MF
applications : List of CardApplications present on card
sw : List of status word definitions
shell_cmdsets : List of cmd2 shell command sets of profile-specific commands
cla : class byte that should be used with cards of this profile
sel_ctrl : selection control bytes class byte that should be used with cards of this profile
"""
self.name = name
self.desc = kw.get("desc", None)
self.files_in_mf = kw.get("files_in_mf", [])
self.sw = kw.get("sw", {})
self.applications = kw.get("applications", [])
self.shell_cmdsets = kw.get("shell_cmdsets", [])
self.cla = kw.get("cla", "00")
self.sel_ctrl = kw.get("sel_ctrl", "0004")
def __str__(self):
return self.name
def __str__(self):
return self.name
def add_application(self, app:CardApplication):
"""Add an application to a card profile.
def add_application(self, app: CardApplication):
"""Add an application to a card profile.
Args:
app : CardApplication instance to be added to profile
"""
self.applications.append(app)
Args:
app : CardApplication instance to be added to profile
"""
self.applications.append(app)
def interpret_sw(self, sw:str):
"""Interpret a given status word within the profile.
def interpret_sw(self, sw: str):
"""Interpret a given status word within the profile.
Args:
sw : Status word as string of 4 hex digits
Args:
sw : Status word as string of 4 hex digits
Returns:
Tuple of two strings
"""
return interpret_sw(self.sw, sw)
Returns:
Tuple of two strings
"""
return interpret_sw(self.sw, sw)
@staticmethod
def decode_select_response(data_hex:str) -> object:
"""Decode the response to a SELECT command.
@staticmethod
def decode_select_response(data_hex: str) -> object:
"""Decode the response to a SELECT command.
This is the fall-back method which doesn't perform any decoding. It mostly
exists so specific derived classes can overload it for actual decoding.
This method is implemented in the profile and is only used when application
specific decoding cannot be performed (no ADF is selected).
This is the fall-back method which doesn't perform any decoding. It mostly
exists so specific derived classes can overload it for actual decoding.
This method is implemented in the profile and is only used when application
specific decoding cannot be performed (no ADF is selected).
Args:
data_hex: Hex string of the select response
"""
return data_hex
Args:
data_hex: Hex string of the select response
"""
return data_hex
@staticmethod
@abc.abstractmethod
def match_with_card(scc:SimCardCommands) -> bool:
"""Check if the specific profile matches the card. This method is a
placeholder that is overloaded by specific dirived classes. The method
actively probes the card to make sure the profile class matches the
physical card. This usually also means that the card is reset during
the process, so this method must not be called at random times. It may
only be called on startup.
@staticmethod
@abc.abstractmethod
def match_with_card(scc: SimCardCommands) -> bool:
"""Check if the specific profile matches the card. This method is a
placeholder that is overloaded by specific dirived classes. The method
actively probes the card to make sure the profile class matches the
physical card. This usually also means that the card is reset during
the process, so this method must not be called at random times. It may
only be called on startup.
Args:
scc: SimCardCommands class
Returns:
match = True, no match = False
"""
return False
Args:
scc: SimCardCommands class
Returns:
match = True, no match = False
"""
return False
@staticmethod
def pick(scc:SimCardCommands):
profiles = list(all_subclasses(CardProfile))
profiles.sort(key=operator.attrgetter('ORDER'))
@staticmethod
def pick(scc: SimCardCommands):
profiles = list(all_subclasses(CardProfile))
profiles.sort(key=operator.attrgetter('ORDER'))
for p in profiles:
if p.match_with_card(scc):
return p()
for p in profiles:
if p.match_with_card(scc):
return p()
return None
return None

View File

@ -43,21 +43,23 @@ mac_length = {
1: 4
}
class EF_PIN(TransparentEF):
def __init__(self, fid, name):
super().__init__(fid, name=name, desc='%s PIN file' % name)
def _decode_bin(self, raw_bin_data):
u = unpack('!BBB8s', raw_bin_data[:11])
res = {'enabled': (True, False)[u[0] & 0x01],
'initialized': (True, False)[u[0] & 0x02],
'disable_able': (False, True)[u[0] & 0x10],
'unblock_able': (False, True)[u[0] & 0x20],
'change_able': (False, True)[u[0] & 0x40],
'valid': (False, True)[u[0] & 0x80],
'attempts_remaining': u[1],
'maximum_attempts': u[2],
'pin': u[3].hex(),
}
res = {'enabled': (True, False)[u[0] & 0x01],
'initialized': (True, False)[u[0] & 0x02],
'disable_able': (False, True)[u[0] & 0x10],
'unblock_able': (False, True)[u[0] & 0x20],
'change_able': (False, True)[u[0] & 0x40],
'valid': (False, True)[u[0] & 0x80],
'attempts_remaining': u[1],
'maximum_attempts': u[2],
'pin': u[3].hex(),
}
if len(raw_bin_data) == 21:
u2 = unpack('!BB8s', raw_bin_data[11:10])
res['attempts_remaining_puk'] = u2[0]
@ -65,9 +67,11 @@ class EF_PIN(TransparentEF):
res['puk'] = u2[2].hex()
return res
class EF_MILENAGE_CFG(TransparentEF):
def __init__(self, fid='6f21', name='EF.MILENAGE_CFG', desc='Milenage connfiguration'):
super().__init__(fid, name=name, desc=desc)
def _decode_bin(self, raw_bin_data):
u = unpack('!BBBBB16s16s16s16s16s', raw_bin_data)
return {'r1': u[0], 'r2': u[1], 'r3': u[2], 'r4': u[3], 'r5': u[4],
@ -76,11 +80,13 @@ class EF_MILENAGE_CFG(TransparentEF):
'c3': u[7].hex(),
'c4': u[8].hex(),
'c5': u[9].hex(),
}
}
class EF_0348_KEY(LinFixedEF):
def __init__(self, fid='6f22', name='EF.0348_KEY', desc='TS 03.48 OTA Keys'):
super().__init__(fid, name=name, desc=desc, rec_len={27,35})
super().__init__(fid, name=name, desc=desc, rec_len={27, 35})
def _decode_record_bin(self, raw_bin_data):
u = unpack('!BBB', raw_bin_data[0:3])
key_algo = (u[2] >> 6) & 1
@ -92,34 +98,42 @@ class EF_0348_KEY(LinFixedEF):
'algorithm': key_algo2str[key_algo],
'mac_length': mac_length[(u[2] >> 7)],
'key': raw_bin_data[3:key_length].hex()
}
}
class EF_0348_COUNT(LinFixedEF):
def __init__(self, fid='6f23', name='EF.0348_COUNT', desc='TS 03.48 OTA Counters'):
super().__init__(fid, name=name, desc=desc, rec_len={7,7})
super().__init__(fid, name=name, desc=desc, rec_len={7, 7})
def _decode_record_bin(self, raw_bin_data):
u = unpack('!BB5s', raw_bin_data)
return {'sec_domain': u[0], 'key_set_version': u[1], 'counter': u[2]}
class EF_SIM_AUTH_COUNTER(TransparentEF):
def __init__(self, fid='af24', name='EF.SIM_AUTH_COUNTER'):
super().__init__(fid, name=name, desc='Number of remaining RUN GSM ALGORITHM executions')
self._construct = Struct('num_run_gsm_algo_remain'/Int32ub)
class EF_GP_COUNT(LinFixedEF):
def __init__(self, fid='6f26', name='EF.GP_COUNT', desc='GP SCP02 Counters'):
super().__init__(fid, name=name, desc=desc, rec_len={5,5})
super().__init__(fid, name=name, desc=desc, rec_len={5, 5})
def _decode_record_bin(self, raw_bin_data):
u = unpack('!BBHB', raw_bin_data)
return {'sec_domain': u[0], 'key_set_version': u[1], 'counter': u[2], 'rfu': u[3]}
class EF_GP_DIV_DATA(LinFixedEF):
def __init__(self, fid='6f27', name='EF.GP_DIV_DATA', desc='GP SCP02 key diversification data'):
super().__init__(fid, name=name, desc=desc, rec_len={12,12})
super().__init__(fid, name=name, desc=desc, rec_len={12, 12})
def _decode_record_bin(self, raw_bin_data):
u = unpack('!BB8s', raw_bin_data)
return {'sec_domain': u[0], 'key_set_version': u[1], 'key_div_data': u[2].hex()}
class EF_SIM_AUTH_KEY(TransparentEF):
def __init__(self, fid='6f20', name='EF.SIM_AUTH_KEY'):
super().__init__(fid, name=name, desc='USIM authentication key')
@ -129,10 +143,15 @@ class EF_SIM_AUTH_KEY(TransparentEF):
'algorithm'/Enum(Nibble, milenage=4, comp128v1=1, comp128v2=2, comp128v3=3))
self._construct = Struct('cfg'/CfgByte,
'key'/Bytes(16),
'op'/If(this.cfg.algorithm=='milenage' and not this.cfg.use_opc_instead_of_op, Bytes(16)),
'opc'/If(this.cfg.algorithm=='milenage' and this.cfg.use_opc_instead_of_op, Bytes(16))
'op' /
If(this.cfg.algorithm == 'milenage' and not this.cfg.use_opc_instead_of_op, Bytes(
16)),
'opc' /
If(this.cfg.algorithm == 'milenage' and this.cfg.use_opc_instead_of_op, Bytes(
16))
)
class DF_SYSTEM(CardDF):
def __init__(self):
super().__init__(fid='a515', name='DF.SYSTEM', desc='CardOS specifics')
@ -150,12 +169,13 @@ class DF_SYSTEM(CardDF):
EF_0348_COUNT(),
EF_GP_COUNT(),
EF_GP_DIV_DATA(),
]
]
self.add_files(files)
def decode_select_response(self, resp_hex):
return pySim.ts_102_221.CardProfileUICC.decode_select_response(resp_hex)
class EF_USIM_SQN(TransparentEF):
def __init__(self, fid='af30', name='EF.USIM_SQN'):
super().__init__(fid, name=name, desc='SQN parameters for AKA')
@ -165,9 +185,11 @@ class EF_USIM_SQN(TransparentEF):
Flag2 = BitStruct('rfu'/BitsRFU(5), 'dont_clear_amf_for_macs'/Bit,
'aus_concealed'/Bit, 'autn_concealed'/Bit)
self._construct = Struct('flag1'/Flag1, 'flag2'/Flag2,
'delta_max'/BytesInteger(6), 'age_limit'/BytesInteger(6),
'delta_max' /
BytesInteger(6), 'age_limit'/BytesInteger(6),
'freshness'/GreedyRange(BytesInteger(6)))
class EF_USIM_AUTH_KEY(TransparentEF):
def __init__(self, fid='af20', name='EF.USIM_AUTH_KEY'):
super().__init__(fid, name=name, desc='USIM authentication key')
@ -177,9 +199,15 @@ class EF_USIM_AUTH_KEY(TransparentEF):
'algorithm'/Enum(Nibble, milenage=4, sha1_aka=5, xor=15))
self._construct = Struct('cfg'/CfgByte,
'key'/Bytes(16),
'op'/If(this.cfg.algorithm=='milenage' and not this.cfg.use_opc_instead_of_op, Bytes(16)),
'opc'/If(this.cfg.algorithm=='milenage' and this.cfg.use_opc_instead_of_op, Bytes(16))
'op' /
If(this.cfg.algorithm == 'milenage' and not this.cfg.use_opc_instead_of_op, Bytes(
16)),
'opc' /
If(this.cfg.algorithm == 'milenage' and this.cfg.use_opc_instead_of_op, Bytes(
16))
)
class EF_USIM_AUTH_KEY_2G(TransparentEF):
def __init__(self, fid='af22', name='EF.USIM_AUTH_KEY_2G'):
super().__init__(fid, name=name, desc='USIM authentication key in 2G context')
@ -189,33 +217,42 @@ class EF_USIM_AUTH_KEY_2G(TransparentEF):
'algorithm'/Enum(Nibble, milenage=4, comp128v1=1, comp128v2=2, comp128v3=3))
self._construct = Struct('cfg'/CfgByte,
'key'/Bytes(16),
'op'/If(this.cfg.algorithm=='milenage' and not this.cfg.use_opc_instead_of_op, Bytes(16)),
'opc'/If(this.cfg.algorithm=='milenage' and this.cfg.use_opc_instead_of_op, Bytes(16))
'op' /
If(this.cfg.algorithm == 'milenage' and not this.cfg.use_opc_instead_of_op, Bytes(
16)),
'opc' /
If(this.cfg.algorithm == 'milenage' and this.cfg.use_opc_instead_of_op, Bytes(
16))
)
class EF_GBA_SK(TransparentEF):
def __init__(self, fid='af31', name='EF.GBA_SK'):
super().__init__(fid, name=name, desc='Secret key for GBA key derivation')
self._construct = GreedyBytes
class EF_GBA_REC_LIST(TransparentEF):
def __init__(self, fid='af32', name='EF.GBA_REC_LIST'):
super().__init__(fid, name=name, desc='Secret key for GBA key derivation')
# integers representing record numbers in EF-GBANL
self._construct = GreedyRange(Int8ub)
class EF_GBA_INT_KEY(LinFixedEF):
def __init__(self, fid='af33', name='EF.GBA_INT_KEY'):
super().__init__(fid, name=name, desc='Secret key for GBA key derivation', rec_len={32,32})
super().__init__(fid, name=name,
desc='Secret key for GBA key derivation', rec_len={32, 32})
self._construct = GreedyBytes
class SysmocomSJA2(CardModel):
_atrs = [ "3B 9F 96 80 1F 87 80 31 E0 73 FE 21 1B 67 4A 4C 75 30 34 05 4B A9",
"3B 9F 96 80 1F 87 80 31 E0 73 FE 21 1B 67 4A 4C 75 31 33 02 51 B2",
"3B 9F 96 80 1F 87 80 31 E0 73 FE 21 1B 67 4A 4C 52 75 31 04 51 D5" ]
_atrs = ["3B 9F 96 80 1F 87 80 31 E0 73 FE 21 1B 67 4A 4C 75 30 34 05 4B A9",
"3B 9F 96 80 1F 87 80 31 E0 73 FE 21 1B 67 4A 4C 75 31 33 02 51 B2",
"3B 9F 96 80 1F 87 80 31 E0 73 FE 21 1B 67 4A 4C 52 75 31 04 51 D5"]
@classmethod
def add_files(cls, rs:RuntimeState):
def add_files(cls, rs: RuntimeState):
"""Add sysmocom SJA2 specific files to given RuntimeState."""
rs.mf.add_file(DF_SYSTEM())
# optional USIM application
@ -228,7 +265,7 @@ class SysmocomSJA2(CardModel):
EF_GBA_REC_LIST(),
EF_GBA_INT_KEY(),
EF_USIM_SQN(),
]
]
usim_adf.add_files(files_adf_usim)
# optional ISIM application
if 'a0000000871004' in rs.mf.applications:
@ -237,5 +274,5 @@ class SysmocomSJA2(CardModel):
EF_USIM_AUTH_KEY(name='EF.ISIM_AUTH_KEY'),
EF_USIM_AUTH_KEY_2G(name='EF.ISIM_AUTH_KEY_2G'),
EF_USIM_SQN(name='EF.ISIM_SQN'),
]
]
isim_adf.add_files(files_adf_isim)

View File

@ -32,6 +32,7 @@ from pySim.exceptions import *
import inspect
import abc
class TlvMeta(abc.ABCMeta):
"""Metaclass which we use to set some class variables at the time of defining a subclass.
This allows us to create subclasses for each TLV/IE type, where the class represents fixed
@ -54,6 +55,7 @@ class TlvMeta(abc.ABCMeta):
x.nested_collection_cls = cls
return x
class TlvCollectionMeta(abc.ABCMeta):
"""Metaclass which we use to set some class variables at the time of defining a subclass.
This allows us to create subclasses for each Collection type, where the class represents fixed
@ -72,6 +74,7 @@ class Transcodable(abc.ABC):
* via a 'construct' object stored in a derived class' _construct variable, or
* via a 'construct' object stored in an instance _construct variable, or
* via a derived class' _{to,from}_bytes() methods."""
def __init__(self):
self.encoded = None
self.decoded = None
@ -95,7 +98,7 @@ class Transcodable(abc.ABC):
def _to_bytes(self):
raise NotImplementedError
def from_bytes(self, do:bytes):
def from_bytes(self, do: bytes):
"""Convert from binary bytes to internal representation. Store the decoded result
in the internal state and return it."""
self.encoded = do
@ -110,9 +113,10 @@ class Transcodable(abc.ABC):
return self.decoded
# not an abstractmethod, as it is only required if no _construct exists
def _from_bytes(self, do:bytes):
def _from_bytes(self, do: bytes):
raise NotImplementedError
class IE(Transcodable, metaclass=TlvMeta):
# we specify the metaclass so any downstream subclasses will automatically use it
"""Base class for various Information Elements. We understand the notion of a hierarchy
@ -146,7 +150,7 @@ class IE(Transcodable, metaclass=TlvMeta):
v = self.decoded
return {type(self).__name__: v}
def from_dict(self, decoded:dict):
def from_dict(self, decoded: dict):
"""Set the IE internal decoded representation to data from the argument.
If this is a nested IE, the child IE instance list is re-created."""
if self.nested_collection:
@ -177,7 +181,7 @@ class IE(Transcodable, metaclass=TlvMeta):
else:
return super().to_bytes()
def from_bytes(self, do:bytes):
def from_bytes(self, do: bytes):
"""Parse _the value part_ from binary bytes to internal representation."""
if self.nested_collection:
self.children = self.nested_collection.from_bytes(do)
@ -188,6 +192,7 @@ class IE(Transcodable, metaclass=TlvMeta):
class TLV_IE(IE):
"""Abstract base class for various TLV type Information Elements."""
def __init__(self, **kwargs):
super().__init__(**kwargs)
@ -197,12 +202,12 @@ class TLV_IE(IE):
@classmethod
@abc.abstractmethod
def _parse_tag_raw(cls, do:bytes) -> Tuple[int, bytes]:
def _parse_tag_raw(cls, do: bytes) -> Tuple[int, bytes]:
"""Obtain the raw TAG at the start of the bytes provided by the user."""
@classmethod
@abc.abstractmethod
def _parse_len(cls, do:bytes) -> Tuple[int, bytes]:
def _parse_len(cls, do: bytes) -> Tuple[int, bytes]:
"""Obtain the length encoded at the start of the bytes provided by the user."""
@abc.abstractmethod
@ -210,7 +215,7 @@ class TLV_IE(IE):
"""Encode the tag part. Must be provided by derived (TLV format specific) class."""
@abc.abstractmethod
def _encode_len(self, val:bytes) -> bytes:
def _encode_len(self, val: bytes) -> bytes:
"""Encode the length part assuming a certain binary value. Must be provided by
derived (TLV format specific) class."""
@ -222,7 +227,7 @@ class TLV_IE(IE):
val = self.to_bytes()
return self._encode_tag() + self._encode_len(val) + val
def from_tlv(self, do:bytes):
def from_tlv(self, do: bytes):
(rawtag, remainder) = self.__class__._parse_tag_raw(do)
if rawtag:
if rawtag != self.tag:
@ -240,50 +245,52 @@ class TLV_IE(IE):
class BER_TLV_IE(TLV_IE):
"""TLV_IE formatted as ASN.1 BER described in ITU-T X.690 8.1.2."""
def __init__(self, **kwargs):
super().__init__(**kwargs)
@classmethod
def _decode_tag(cls, do:bytes) -> Tuple[dict, bytes]:
def _decode_tag(cls, do: bytes) -> Tuple[dict, bytes]:
return bertlv_parse_tag(do)
@classmethod
def _parse_tag_raw(cls, do:bytes) -> Tuple[int, bytes]:
def _parse_tag_raw(cls, do: bytes) -> Tuple[int, bytes]:
return bertlv_parse_tag_raw(do)
@classmethod
def _parse_len(cls, do:bytes) -> Tuple[int, bytes]:
def _parse_len(cls, do: bytes) -> Tuple[int, bytes]:
return bertlv_parse_len(do)
def _encode_tag(self) -> bytes:
return bertlv_encode_tag(self._compute_tag())
def _encode_len(self, val:bytes) -> bytes:
def _encode_len(self, val: bytes) -> bytes:
return bertlv_encode_len(len(val))
class COMPR_TLV_IE(TLV_IE):
"""TLV_IE formated as COMPREHENSION-TLV as described in ETSI TS 101 220."""
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.comprehension = False
@classmethod
def _decode_tag(cls, do:bytes) -> Tuple[dict, bytes]:
def _decode_tag(cls, do: bytes) -> Tuple[dict, bytes]:
return comprehensiontlv_parse_tag(do)
@classmethod
def _parse_tag_raw(cls, do:bytes) -> Tuple[int, bytes]:
def _parse_tag_raw(cls, do: bytes) -> Tuple[int, bytes]:
return comprehensiontlv_parse_tag_raw(do)
@classmethod
def _parse_len(cls, do:bytes) -> Tuple[int, bytes]:
def _parse_len(cls, do: bytes) -> Tuple[int, bytes]:
return bertlv_parse_len(do)
def _encode_tag(self) -> bytes:
return comprehensiontlv_encode_tag(self._compute_tag())
def _encode_len(self, val:bytes) -> bytes:
def _encode_len(self, val: bytes) -> bytes:
return bertlv_encode_len(len(val))
@ -294,14 +301,15 @@ class TLV_IE_Collection(metaclass=TlvCollectionMeta):
of each DO."""
# this is overridden by the TlvCollectionMeta metaclass, if it is used to create subclasses
possible_nested = []
def __init__(self, desc=None, **kwargs):
self.desc = desc
#print("possible_nested: ", self.possible_nested)
self.members = kwargs.get('nested', self.possible_nested)
self.members_by_tag = {}
self.members_by_name = {}
self.members_by_tag = { m.tag:m for m in self.members }
self.members_by_name = { m.__name__:m for m in self.members }
self.members_by_tag = {m.tag: m for m in self.members}
self.members_by_name = {m.__name__: m for m in self.members}
# if we are a constructed IE, [ordered] list of actual child-IE instances
self.children = kwargs.get('children', [])
self.encoded = None
@ -322,11 +330,11 @@ class TLV_IE_Collection(metaclass=TlvCollectionMeta):
return TLV_IE_Collection(self.desc, nested=members)
elif inspect.isclass(other) and issubclass(other, TLV_IE):
# adding a member to a collection
return TLV_IE_Collection(self.desc, nested = self.members + [other])
return TLV_IE_Collection(self.desc, nested=self.members + [other])
else:
raise TypeError
def from_bytes(self, binary:bytes) -> List[TLV_IE]:
def from_bytes(self, binary: bytes) -> List[TLV_IE]:
"""Create a list of TLV_IEs from the collection based on binary input data.
Args:
binary : binary bytes of encoded data
@ -353,9 +361,9 @@ class TLV_IE_Collection(metaclass=TlvCollectionMeta):
else:
# unknown tag; create the related class on-the-fly using the same base class
name = 'unknown_%s_%X' % (first.__base__.__name__, tag)
cls = type(name, (first.__base__,), {'tag':tag, 'possible_nested':[],
'nested_collection_cls':None})
cls._from_bytes = lambda s, a : {'raw': a.hex()}
cls = type(name, (first.__base__,), {'tag': tag, 'possible_nested': [],
'nested_collection_cls': None})
cls._from_bytes = lambda s, a: {'raw': a.hex()}
cls._to_bytes = lambda s: bytes.fromhex(s.decoded['raw'])
# create an instance and parse accordingly
inst = cls()
@ -364,7 +372,7 @@ class TLV_IE_Collection(metaclass=TlvCollectionMeta):
self.children = res
return res
def from_dict(self, decoded:List[dict]) -> List[TLV_IE]:
def from_dict(self, decoded: List[dict]) -> List[TLV_IE]:
"""Create a list of TLV_IE instances from the collection based on an array
of dicts, where they key indicates the name of the TLV_IE subclass to use."""
# list of instances of TLV_IE collection member classes appearing in the data

View File

@ -29,217 +29,224 @@ from pySim.utils import sw_match, b2h, h2b, i2h
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
class ApduTracer:
def trace_command(self, cmd):
pass
def trace_response(self, cmd, sw, resp):
pass
class ApduTracer:
def trace_command(self, cmd):
pass
def trace_response(self, cmd, sw, resp):
pass
class LinkBase(abc.ABC):
"""Base class for link/transport to card."""
"""Base class for link/transport to card."""
def __init__(self, sw_interpreter=None, apdu_tracer=None):
self.sw_interpreter = sw_interpreter
self.apdu_tracer = apdu_tracer
def __init__(self, sw_interpreter=None, apdu_tracer=None):
self.sw_interpreter = sw_interpreter
self.apdu_tracer = apdu_tracer
@abc.abstractmethod
def _send_apdu_raw(self, pdu:str) -> Tuple[str, str]:
"""Implementation specific method for sending the PDU."""
@abc.abstractmethod
def _send_apdu_raw(self, pdu: str) -> Tuple[str, str]:
"""Implementation specific method for sending the PDU."""
def set_sw_interpreter(self, interp):
"""Set an (optional) status word interpreter."""
self.sw_interpreter = interp
def set_sw_interpreter(self, interp):
"""Set an (optional) status word interpreter."""
self.sw_interpreter = interp
@abc.abstractmethod
def wait_for_card(self, timeout:int=None, newcardonly:bool=False):
"""Wait for a card and connect to it
@abc.abstractmethod
def wait_for_card(self, timeout: int = None, newcardonly: bool = False):
"""Wait for a card and connect to it
Args:
timeout : Maximum wait time in seconds (None=no timeout)
newcardonly : Should we wait for a new card, or an already inserted one ?
"""
Args:
timeout : Maximum wait time in seconds (None=no timeout)
newcardonly : Should we wait for a new card, or an already inserted one ?
"""
@abc.abstractmethod
def connect(self):
"""Connect to a card immediately
"""
@abc.abstractmethod
def connect(self):
"""Connect to a card immediately
"""
@abc.abstractmethod
def disconnect(self):
"""Disconnect from card
"""
@abc.abstractmethod
def disconnect(self):
"""Disconnect from card
"""
@abc.abstractmethod
def reset_card(self):
"""Resets the card (power down/up)
"""
@abc.abstractmethod
def reset_card(self):
"""Resets the card (power down/up)
"""
def send_apdu_raw(self, pdu:str):
"""Sends an APDU with minimal processing
def send_apdu_raw(self, pdu: str):
"""Sends an APDU with minimal processing
Args:
pdu : string of hexadecimal characters (ex. "A0A40000023F00")
Returns:
tuple(data, sw), where
data : string (in hex) of returned data (ex. "074F4EFFFF")
sw : string (in hex) of status word (ex. "9000")
"""
if self.apdu_tracer:
self.apdu_tracer.trace_command(pdu)
(data, sw) = self._send_apdu_raw(pdu)
if self.apdu_tracer:
self.apdu_tracer.trace_response(pdu, sw, data)
return (data, sw)
Args:
pdu : string of hexadecimal characters (ex. "A0A40000023F00")
Returns:
tuple(data, sw), where
data : string (in hex) of returned data (ex. "074F4EFFFF")
sw : string (in hex) of status word (ex. "9000")
"""
if self.apdu_tracer:
self.apdu_tracer.trace_command(pdu)
(data, sw) = self._send_apdu_raw(pdu)
if self.apdu_tracer:
self.apdu_tracer.trace_response(pdu, sw, data)
return (data, sw)
def send_apdu(self, pdu):
"""Sends an APDU and auto fetch response data
def send_apdu(self, pdu):
"""Sends an APDU and auto fetch response data
Args:
pdu : string of hexadecimal characters (ex. "A0A40000023F00")
Returns:
tuple(data, sw), where
data : string (in hex) of returned data (ex. "074F4EFFFF")
sw : string (in hex) of status word (ex. "9000")
"""
data, sw = self.send_apdu_raw(pdu)
Args:
pdu : string of hexadecimal characters (ex. "A0A40000023F00")
Returns:
tuple(data, sw), where
data : string (in hex) of returned data (ex. "074F4EFFFF")
sw : string (in hex) of status word (ex. "9000")
"""
data, sw = self.send_apdu_raw(pdu)
# When whe have sent the first APDU, the SW may indicate that there are response bytes
# available. There are two SWs commonly used for this 9fxx (sim) and 61xx (usim), where
# xx is the number of response bytes available.
# See also:
if (sw is not None):
if ((sw[0:2] == '9f') or (sw[0:2] == '61')):
# SW1=9F: 3GPP TS 51.011 9.4.1, Responses to commands which are correctly executed
# SW1=61: ISO/IEC 7816-4, Table 5 — General meaning of the interindustry values of SW1-SW2
pdu_gr = pdu[0:2] + 'c00000' + sw[2:4]
data, sw = self.send_apdu_raw(pdu_gr)
if sw[0:2] == '6c':
# SW1=6C: ETSI TS 102 221 Table 7.1: Procedure byte coding
pdu_gr = pdu[0:8] + sw[2:4]
data,sw = self.send_apdu_raw(pdu_gr)
# When we have sent the first APDU, the SW may indicate that there are response bytes
# available. There are two SWs commonly used for this 9fxx (sim) and 61xx (usim), where
# xx is the number of response bytes available.
# See also:
if (sw is not None):
if ((sw[0:2] == '9f') or (sw[0:2] == '61')):
# SW1=9F: 3GPP TS 51.011 9.4.1, Responses to commands which are correctly executed
# SW1=61: ISO/IEC 7816-4, Table 5 — General meaning of the interindustry values of SW1-SW2
pdu_gr = pdu[0:2] + 'c00000' + sw[2:4]
data, sw = self.send_apdu_raw(pdu_gr)
if sw[0:2] == '6c':
# SW1=6C: ETSI TS 102 221 Table 7.1: Procedure byte coding
pdu_gr = pdu[0:8] + sw[2:4]
data, sw = self.send_apdu_raw(pdu_gr)
return data, sw
return data, sw
def send_apdu_checksw(self, pdu, sw="9000"):
"""Sends an APDU and check returned SW
def send_apdu_checksw(self, pdu, sw="9000"):
"""Sends an APDU and check returned SW
Args:
pdu : string of hexadecimal characters (ex. "A0A40000023F00")
sw : string of 4 hexadecimal characters (ex. "9000"). The user may mask out certain
digits using a '?' to add some ambiguity if needed.
Returns:
tuple(data, sw), where
data : string (in hex) of returned data (ex. "074F4EFFFF")
sw : string (in hex) of status word (ex. "9000")
"""
rv = self.send_apdu(pdu)
Args:
pdu : string of hexadecimal characters (ex. "A0A40000023F00")
sw : string of 4 hexadecimal characters (ex. "9000"). The user may mask out certain
digits using a '?' to add some ambiguity if needed.
Returns:
tuple(data, sw), where
data : string (in hex) of returned data (ex. "074F4EFFFF")
sw : string (in hex) of status word (ex. "9000")
"""
rv = self.send_apdu(pdu)
if sw == '9000' and sw_match(rv[1], '91xx'):
# proactive sim as per TS 102 221 Setion 7.4.2
rv = self.send_apdu_checksw('80120000' + rv[1][2:], sw)
print("FETCH: %s", rv[0])
if not sw_match(rv[1], sw):
raise SwMatchError(rv[1], sw.lower(), self.sw_interpreter)
return rv
if sw == '9000' and sw_match(rv[1], '91xx'):
# proactive sim as per TS 102 221 Setion 7.4.2
rv = self.send_apdu_checksw('80120000' + rv[1][2:], sw)
print("FETCH: %s", rv[0])
if not sw_match(rv[1], sw):
raise SwMatchError(rv[1], sw.lower(), self.sw_interpreter)
return rv
def send_apdu_constr(self, cla, ins, p1, p2, cmd_constr, cmd_data, resp_constr):
"""Build and sends an APDU using a 'construct' definition; parses response.
def send_apdu_constr(self, cla, ins, p1, p2, cmd_constr, cmd_data, resp_constr):
"""Build and sends an APDU using a 'construct' definition; parses response.
Args:
cla : string (in hex) ISO 7816 class byte
ins : string (in hex) ISO 7816 instruction byte
p1 : string (in hex) ISO 7116 Parameter 1 byte
p2 : string (in hex) ISO 7116 Parameter 2 byte
cmd_cosntr : defining how to generate binary APDU command data
cmd_data : command data passed to cmd_constr
resp_cosntr : defining how to decode binary APDU response data
Returns:
Tuple of (decoded_data, sw)
"""
cmd = cmd_constr.build(cmd_data) if cmd_data else ''
p3 = i2h([len(cmd)])
pdu = ''.join([cla, ins, p1, p2, p3, b2h(cmd)])
(data, sw) = self.send_apdu(pdu)
if data:
# filter the resulting dict to avoid '_io' members inside
rsp = filter_dict(resp_constr.parse(h2b(data)))
else:
rsp = None
return (rsp, sw)
Args:
cla : string (in hex) ISO 7816 class byte
ins : string (in hex) ISO 7816 instruction byte
p1 : string (in hex) ISO 7116 Parameter 1 byte
p2 : string (in hex) ISO 7116 Parameter 2 byte
cmd_cosntr : defining how to generate binary APDU command data
cmd_data : command data passed to cmd_constr
resp_cosntr : defining how to decode binary APDU response data
Returns:
Tuple of (decoded_data, sw)
"""
cmd = cmd_constr.build(cmd_data) if cmd_data else ''
p3 = i2h([len(cmd)])
pdu = ''.join([cla, ins, p1, p2, p3, b2h(cmd)])
(data, sw) = self.send_apdu(pdu)
if data:
# filter the resulting dict to avoid '_io' members inside
rsp = filter_dict(resp_constr.parse(h2b(data)))
else:
rsp = None
return (rsp, sw)
def send_apdu_constr_checksw(self, cla, ins, p1, p2, cmd_constr, cmd_data, resp_constr,
sw_exp="9000"):
"""Build and sends an APDU using a 'construct' definition; parses response.
def send_apdu_constr_checksw(self, cla, ins, p1, p2, cmd_constr, cmd_data, resp_constr,
sw_exp="9000"):
"""Build and sends an APDU using a 'construct' definition; parses response.
Args:
cla : string (in hex) ISO 7816 class byte
ins : string (in hex) ISO 7816 instruction byte
p1 : string (in hex) ISO 7116 Parameter 1 byte
p2 : string (in hex) ISO 7116 Parameter 2 byte
cmd_cosntr : defining how to generate binary APDU command data
cmd_data : command data passed to cmd_constr
resp_cosntr : defining how to decode binary APDU response data
exp_sw : string (in hex) of status word (ex. "9000")
Returns:
Tuple of (decoded_data, sw)
"""
(rsp, sw) = self.send_apdu_constr(cla, ins,
p1, p2, cmd_constr, cmd_data, resp_constr)
if not sw_match(sw, sw_exp):
raise SwMatchError(sw, sw_exp.lower(), self.sw_interpreter)
return (rsp, sw)
Args:
cla : string (in hex) ISO 7816 class byte
ins : string (in hex) ISO 7816 instruction byte
p1 : string (in hex) ISO 7116 Parameter 1 byte
p2 : string (in hex) ISO 7116 Parameter 2 byte
cmd_cosntr : defining how to generate binary APDU command data
cmd_data : command data passed to cmd_constr
resp_cosntr : defining how to decode binary APDU response data
exp_sw : string (in hex) of status word (ex. "9000")
Returns:
Tuple of (decoded_data, sw)
"""
(rsp, sw) = self.send_apdu_constr(cla, ins, p1, p2, cmd_constr, cmd_data, resp_constr)
if not sw_match(sw, sw_exp):
raise SwMatchError(sw, sw_exp.lower(), self.sw_interpreter)
return (rsp, sw)
def argparse_add_reader_args(arg_parser):
"""Add all reader related arguments to the given argparse.Argumentparser instance."""
serial_group = arg_parser.add_argument_group('Serial Reader')
serial_group.add_argument('-d', '--device', metavar='DEV', default='/dev/ttyUSB0',
help='Serial Device for SIM access')
serial_group.add_argument('-b', '--baud', dest='baudrate', type=int, metavar='BAUD', default=9600,
help='Baud rate used for SIM access')
"""Add all reader related arguments to the given argparse.Argumentparser instance."""
serial_group = arg_parser.add_argument_group('Serial Reader')
serial_group.add_argument('-d', '--device', metavar='DEV', default='/dev/ttyUSB0',
help='Serial Device for SIM access')
serial_group.add_argument('-b', '--baud', dest='baudrate', type=int, metavar='BAUD', default=9600,
help='Baud rate used for SIM access')
pcsc_group = arg_parser.add_argument_group('PC/SC Reader')
pcsc_group.add_argument('-p', '--pcsc-device', type=int, dest='pcsc_dev', metavar='PCSC', default=None,
help='PC/SC reader number to use for SIM access')
pcsc_group = arg_parser.add_argument_group('PC/SC Reader')
pcsc_group.add_argument('-p', '--pcsc-device', type=int, dest='pcsc_dev', metavar='PCSC', default=None,
help='PC/SC reader number to use for SIM access')
modem_group = arg_parser.add_argument_group('AT Command Modem Reader')
modem_group.add_argument('--modem-device', dest='modem_dev', metavar='DEV', default=None,
help='Serial port of modem for Generic SIM Access (3GPP TS 27.007)')
modem_group.add_argument('--modem-baud', type=int, metavar='BAUD', default=115200,
help='Baud rate used for modem port')
modem_group = arg_parser.add_argument_group('AT Command Modem Reader')
modem_group.add_argument('--modem-device', dest='modem_dev', metavar='DEV', default=None,
help='Serial port of modem for Generic SIM Access (3GPP TS 27.007)')
modem_group.add_argument('--modem-baud', type=int, metavar='BAUD', default=115200,
help='Baud rate used for modem port')
osmobb_group = arg_parser.add_argument_group('OsmocomBB Reader')
osmobb_group.add_argument('--osmocon', dest='osmocon_sock', metavar='PATH', default=None,
help='Socket path for Calypso (e.g. Motorola C1XX) based reader (via OsmocomBB)')
osmobb_group = arg_parser.add_argument_group('OsmocomBB Reader')
osmobb_group.add_argument('--osmocon', dest='osmocon_sock', metavar='PATH', default=None,
help='Socket path for Calypso (e.g. Motorola C1XX) based reader (via OsmocomBB)')
return arg_parser
return arg_parser
def init_reader(opts, **kwargs) -> Optional[LinkBase]:
"""
Init card reader driver
"""
sl = None # type : :Optional[LinkBase]
try:
if opts.pcsc_dev is not None:
print("Using PC/SC reader interface")
from pySim.transport.pcsc import PcscSimLink
sl = PcscSimLink(opts.pcsc_dev, **kwargs)
elif opts.osmocon_sock is not None:
print("Using Calypso-based (OsmocomBB) reader interface")
from pySim.transport.calypso import CalypsoSimLink
sl = CalypsoSimLink(sock_path=opts.osmocon_sock, **kwargs)
elif opts.modem_dev is not None:
print("Using modem for Generic SIM Access (3GPP TS 27.007)")
from pySim.transport.modem_atcmd import ModemATCommandLink
sl = ModemATCommandLink(device=opts.modem_dev, baudrate=opts.modem_baud, **kwargs)
else: # Serial reader is default
print("Using serial reader interface")
from pySim.transport.serial import SerialSimLink
sl = SerialSimLink(device=opts.device, baudrate=opts.baudrate, **kwargs)
return sl
except Exception as e:
if str(e):
print("Card reader initialization failed with exception:\n" + str(e))
else:
print("Card reader initialization failed with an exception of type:\n" + str(type(e)))
return None
"""
Init card reader driver
"""
sl = None # type : :Optional[LinkBase]
try:
if opts.pcsc_dev is not None:
print("Using PC/SC reader interface")
from pySim.transport.pcsc import PcscSimLink
sl = PcscSimLink(opts.pcsc_dev, **kwargs)
elif opts.osmocon_sock is not None:
print("Using Calypso-based (OsmocomBB) reader interface")
from pySim.transport.calypso import CalypsoSimLink
sl = CalypsoSimLink(sock_path=opts.osmocon_sock, **kwargs)
elif opts.modem_dev is not None:
print("Using modem for Generic SIM Access (3GPP TS 27.007)")
from pySim.transport.modem_atcmd import ModemATCommandLink
sl = ModemATCommandLink(
device=opts.modem_dev, baudrate=opts.modem_baud, **kwargs)
else: # Serial reader is default
print("Using serial reader interface")
from pySim.transport.serial import SerialSimLink
sl = SerialSimLink(device=opts.device,
baudrate=opts.baudrate, **kwargs)
return sl
except Exception as e:
if str(e):
print("Card reader initialization failed with exception:\n" + str(e))
else:
print(
"Card reader initialization failed with an exception of type:\n" + str(type(e)))
return None

View File

@ -25,127 +25,132 @@ from pySim.transport import LinkBase
from pySim.exceptions import *
from pySim.utils import h2b, b2h
class L1CTLMessage(object):
# Every (encoded) L1CTL message has the following structure:
# - msg_length (2 bytes, net order)
# - l1ctl_hdr (packed structure)
# - msg_type
# - flags
# - padding (2 spare bytes)
# - ... payload ...
# Every (encoded) L1CTL message has the following structure:
# - msg_length (2 bytes, net order)
# - l1ctl_hdr (packed structure)
# - msg_type
# - flags
# - padding (2 spare bytes)
# - ... payload ...
def __init__(self, msg_type, flags = 0x00):
# Init L1CTL message header
self.data = struct.pack("BBxx", msg_type, flags)
def __init__(self, msg_type, flags=0x00):
# Init L1CTL message header
self.data = struct.pack("BBxx", msg_type, flags)
def gen_msg(self):
return struct.pack("!H", len(self.data)) + self.data
def gen_msg(self):
return struct.pack("!H", len(self.data)) + self.data
class L1CTLMessageReset(L1CTLMessage):
# L1CTL message types
L1CTL_RESET_REQ = 0x0d
L1CTL_RESET_IND = 0x07
L1CTL_RESET_CONF = 0x0e
# L1CTL message types
L1CTL_RESET_REQ = 0x0d
L1CTL_RESET_IND = 0x07
L1CTL_RESET_CONF = 0x0e
# Reset types
L1CTL_RES_T_BOOT = 0x00
L1CTL_RES_T_FULL = 0x01
L1CTL_RES_T_SCHED = 0x02
# Reset types
L1CTL_RES_T_BOOT = 0x00
L1CTL_RES_T_FULL = 0x01
L1CTL_RES_T_SCHED = 0x02
def __init__(self, type=L1CTL_RES_T_FULL):
super(L1CTLMessageReset, self).__init__(self.L1CTL_RESET_REQ)
self.data += struct.pack("Bxxx", type)
def __init__(self, type = L1CTL_RES_T_FULL):
super(L1CTLMessageReset, self).__init__(self.L1CTL_RESET_REQ)
self.data += struct.pack("Bxxx", type)
class L1CTLMessageSIM(L1CTLMessage):
# SIM related message types
L1CTL_SIM_REQ = 0x16
L1CTL_SIM_CONF = 0x17
# SIM related message types
L1CTL_SIM_REQ = 0x16
L1CTL_SIM_CONF = 0x17
def __init__(self, pdu):
super(L1CTLMessageSIM, self).__init__(self.L1CTL_SIM_REQ)
self.data += pdu
def __init__(self, pdu):
super(L1CTLMessageSIM, self).__init__(self.L1CTL_SIM_REQ)
self.data += pdu
class CalypsoSimLink(LinkBase):
"""Transport Link for Calypso based phones."""
"""Transport Link for Calypso based phones."""
def __init__(self, sock_path:str = "/tmp/osmocom_l2", **kwargs):
super().__init__(**kwargs)
# Make sure that a given socket path exists
if not os.path.exists(sock_path):
raise ReaderError("There is no such ('%s') UNIX socket" % sock_path)
def __init__(self, sock_path: str = "/tmp/osmocom_l2", **kwargs):
super().__init__(**kwargs)
# Make sure that a given socket path exists
if not os.path.exists(sock_path):
raise ReaderError(
"There is no such ('%s') UNIX socket" % sock_path)
print("Connecting to osmocon at '%s'..." % sock_path)
print("Connecting to osmocon at '%s'..." % sock_path)
# Establish a client connection
self.sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
self.sock.connect(sock_path)
# Establish a client connection
self.sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
self.sock.connect(sock_path)
def __del__(self):
self.sock.close()
def __del__(self):
self.sock.close()
def wait_for_rsp(self, exp_len = 128):
# Wait for incoming data (timeout is 3 seconds)
s, _, _ = select.select([self.sock], [], [], 3.0)
if not s:
raise ReaderError("Timeout waiting for card response")
def wait_for_rsp(self, exp_len=128):
# Wait for incoming data (timeout is 3 seconds)
s, _, _ = select.select([self.sock], [], [], 3.0)
if not s:
raise ReaderError("Timeout waiting for card response")
# Receive expected amount of bytes from osmocon
rsp = self.sock.recv(exp_len)
return rsp
# Receive expected amount of bytes from osmocon
rsp = self.sock.recv(exp_len)
return rsp
def reset_card(self):
# Request FULL reset
req_msg = L1CTLMessageReset()
self.sock.send(req_msg.gen_msg())
def reset_card(self):
# Request FULL reset
req_msg = L1CTLMessageReset()
self.sock.send(req_msg.gen_msg())
# Wait for confirmation
rsp = self.wait_for_rsp()
rsp_msg = struct.unpack_from("!HB", rsp)
if rsp_msg[1] != L1CTLMessageReset.L1CTL_RESET_CONF:
raise ReaderError("Failed to reset Calypso PHY")
# Wait for confirmation
rsp = self.wait_for_rsp()
rsp_msg = struct.unpack_from("!HB", rsp)
if rsp_msg[1] != L1CTLMessageReset.L1CTL_RESET_CONF:
raise ReaderError("Failed to reset Calypso PHY")
def connect(self):
self.reset_card()
def connect(self):
self.reset_card()
def disconnect(self):
pass # Nothing to do really ...
def disconnect(self):
pass # Nothing to do really ...
def wait_for_card(self, timeout = None, newcardonly = False):
pass # Nothing to do really ...
def wait_for_card(self, timeout=None, newcardonly=False):
pass # Nothing to do really ...
def _send_apdu_raw(self, pdu):
def _send_apdu_raw(self, pdu):
# Request FULL reset
req_msg = L1CTLMessageSIM(h2b(pdu))
self.sock.send(req_msg.gen_msg())
# Request FULL reset
req_msg = L1CTLMessageSIM(h2b(pdu))
self.sock.send(req_msg.gen_msg())
# Read message length first
rsp = self.wait_for_rsp(struct.calcsize("!H"))
msg_len = struct.unpack_from("!H", rsp)[0]
if msg_len < struct.calcsize("BBxx"):
raise ReaderError("Missing L1CTL header for L1CTL_SIM_CONF")
# Read message length first
rsp = self.wait_for_rsp(struct.calcsize("!H"))
msg_len = struct.unpack_from("!H", rsp)[0]
if msg_len < struct.calcsize("BBxx"):
raise ReaderError("Missing L1CTL header for L1CTL_SIM_CONF")
# Read the whole message then
rsp = self.sock.recv(msg_len)
# Read the whole message then
rsp = self.sock.recv(msg_len)
# Verify L1CTL header
hdr = struct.unpack_from("BBxx", rsp)
if hdr[0] != L1CTLMessageSIM.L1CTL_SIM_CONF:
raise ReaderError("Unexpected L1CTL message received")
# Verify L1CTL header
hdr = struct.unpack_from("BBxx", rsp)
if hdr[0] != L1CTLMessageSIM.L1CTL_SIM_CONF:
raise ReaderError("Unexpected L1CTL message received")
# Verify the payload length
offset = struct.calcsize("BBxx")
if len(rsp) <= offset:
raise ProtocolError("Empty response from SIM?!?")
# Verify the payload length
offset = struct.calcsize("BBxx")
if len(rsp) <= offset:
raise ProtocolError("Empty response from SIM?!?")
# Omit L1CTL header
rsp = rsp[offset:]
# Omit L1CTL header
rsp = rsp[offset:]
# Unpack data and SW
data = rsp[:-2]
sw = rsp[-2:]
# Unpack data and SW
data = rsp[:-2]
sw = rsp[-2:]
return b2h(data), b2h(sw)
return b2h(data), b2h(sw)

View File

@ -27,138 +27,142 @@ from pySim.exceptions import *
# HACK: if somebody needs to debug this thing
# log.root.setLevel(log.DEBUG)
class ModemATCommandLink(LinkBase):
"""Transport Link for 3GPP TS 27.007 compliant modems."""
def __init__(self, device:str='/dev/ttyUSB0', baudrate:int=115200, **kwargs):
super().__init__(**kwargs)
self._sl = serial.Serial(device, baudrate, timeout=5)
self._echo = False # this will be auto-detected by _check_echo()
self._device = device
self._atr = None
"""Transport Link for 3GPP TS 27.007 compliant modems."""
# Check the AT interface
self._check_echo()
def __init__(self, device: str = '/dev/ttyUSB0', baudrate: int = 115200, **kwargs):
super().__init__(**kwargs)
self._sl = serial.Serial(device, baudrate, timeout=5)
self._echo = False # this will be auto-detected by _check_echo()
self._device = device
self._atr = None
# Trigger initial reset
self.reset_card()
# Check the AT interface
self._check_echo()
def __del__(self):
if hasattr(self, '_sl'):
self._sl.close()
# Trigger initial reset
self.reset_card()
def send_at_cmd(self, cmd, timeout=0.2, patience=0.002):
# Convert from string to bytes, if needed
bcmd = cmd if type(cmd) is bytes else cmd.encode()
bcmd += b'\r'
def __del__(self):
if hasattr(self, '_sl'):
self._sl.close()
# Clean input buffer from previous/unexpected data
self._sl.reset_input_buffer()
def send_at_cmd(self, cmd, timeout=0.2, patience=0.002):
# Convert from string to bytes, if needed
bcmd = cmd if type(cmd) is bytes else cmd.encode()
bcmd += b'\r'
# Send command to the modem
log.debug('Sending AT command: %s', cmd)
try:
wlen = self._sl.write(bcmd)
assert(wlen == len(bcmd))
except:
raise ReaderError('Failed to send AT command: %s' % cmd)
# Clean input buffer from previous/unexpected data
self._sl.reset_input_buffer()
rsp = b''
its = 1
t_start = time.time()
while True:
rsp = rsp + self._sl.read(self._sl.in_waiting)
lines = rsp.split(b'\r\n')
if len(lines) >= 2:
res = lines[-2]
if res == b'OK':
log.debug('Command finished with result: %s', res)
break
if res == b'ERROR' or res.startswith(b'+CME ERROR:'):
log.error('Command failed with result: %s', res)
break
# Send command to the modem
log.debug('Sending AT command: %s', cmd)
try:
wlen = self._sl.write(bcmd)
assert(wlen == len(bcmd))
except:
raise ReaderError('Failed to send AT command: %s' % cmd)
if time.time() - t_start >= timeout:
log.info('Command finished with timeout >= %ss', timeout)
break
time.sleep(patience)
its += 1
log.debug('Command took %0.6fs (%d cycles a %fs)', time.time() - t_start, its, patience)
rsp = b''
its = 1
t_start = time.time()
while True:
rsp = rsp + self._sl.read(self._sl.in_waiting)
lines = rsp.split(b'\r\n')
if len(lines) >= 2:
res = lines[-2]
if res == b'OK':
log.debug('Command finished with result: %s', res)
break
if res == b'ERROR' or res.startswith(b'+CME ERROR:'):
log.error('Command failed with result: %s', res)
break
if self._echo:
# Skip echo chars
rsp = rsp[wlen:]
rsp = rsp.strip()
rsp = rsp.split(b'\r\n\r\n')
if time.time() - t_start >= timeout:
log.info('Command finished with timeout >= %ss', timeout)
break
time.sleep(patience)
its += 1
log.debug('Command took %0.6fs (%d cycles a %fs)',
time.time() - t_start, its, patience)
log.debug('Got response from modem: %s', rsp)
return rsp
if self._echo:
# Skip echo chars
rsp = rsp[wlen:]
rsp = rsp.strip()
rsp = rsp.split(b'\r\n\r\n')
def _check_echo(self):
"""Verify the correct response to 'AT' command
and detect if inputs are echoed by the device
log.debug('Got response from modem: %s', rsp)
return rsp
Although echo of inputs can be enabled/disabled via
ATE1/ATE0, respectively, we rather detect the current
configuration of the modem without any change.
"""
# Next command shall not strip the echo from the response
self._echo = False
result = self.send_at_cmd('AT')
def _check_echo(self):
"""Verify the correct response to 'AT' command
and detect if inputs are echoed by the device
# Verify the response
if len(result) > 0:
if result[-1] == b'OK':
self._echo = False
return
elif result[-1] == b'AT\r\r\nOK':
self._echo = True
return
raise ReaderError('Interface \'%s\' does not respond to \'AT\' command' % self._device)
Although echo of inputs can be enabled/disabled via
ATE1/ATE0, respectively, we rather detect the current
configuration of the modem without any change.
"""
# Next command shall not strip the echo from the response
self._echo = False
result = self.send_at_cmd('AT')
def reset_card(self):
# Reset the modem, just to be sure
if self.send_at_cmd('ATZ') != [b'OK']:
raise ReaderError('Failed to reset the modem')
# Verify the response
if len(result) > 0:
if result[-1] == b'OK':
self._echo = False
return
elif result[-1] == b'AT\r\r\nOK':
self._echo = True
return
raise ReaderError(
'Interface \'%s\' does not respond to \'AT\' command' % self._device)
# Make sure that generic SIM access is supported
if self.send_at_cmd('AT+CSIM=?') != [b'OK']:
raise ReaderError('The modem does not seem to support SIM access')
def reset_card(self):
# Reset the modem, just to be sure
if self.send_at_cmd('ATZ') != [b'OK']:
raise ReaderError('Failed to reset the modem')
log.info('Modem at \'%s\' is ready!' % self._device)
# Make sure that generic SIM access is supported
if self.send_at_cmd('AT+CSIM=?') != [b'OK']:
raise ReaderError('The modem does not seem to support SIM access')
def connect(self):
pass # Nothing to do really ...
log.info('Modem at \'%s\' is ready!' % self._device)
def disconnect(self):
pass # Nothing to do really ...
def connect(self):
pass # Nothing to do really ...
def wait_for_card(self, timeout=None, newcardonly=False):
pass # Nothing to do really ...
def disconnect(self):
pass # Nothing to do really ...
def _send_apdu_raw(self, pdu):
# Make sure pdu has upper case hex digits [A-F]
pdu = pdu.upper()
def wait_for_card(self, timeout=None, newcardonly=False):
pass # Nothing to do really ...
# Prepare the command as described in 8.17
cmd = 'AT+CSIM=%d,\"%s\"' % (len(pdu), pdu)
log.debug('Sending command: %s', cmd)
def _send_apdu_raw(self, pdu):
# Make sure pdu has upper case hex digits [A-F]
pdu = pdu.upper()
# Send AT+CSIM command to the modem
# TODO: also handle +CME ERROR: <err>
rsp = self.send_at_cmd(cmd)
if len(rsp) != 2 or rsp[-1] != b'OK':
raise ReaderError('APDU transfer failed: %s' % str(rsp))
rsp = rsp[0] # Get rid of b'OK'
# Prepare the command as described in 8.17
cmd = 'AT+CSIM=%d,\"%s\"' % (len(pdu), pdu)
log.debug('Sending command: %s', cmd)
# Make sure that the response has format: b'+CSIM: %d,\"%s\"'
try:
result = re.match(b'\+CSIM: (\d+),\"([0-9A-F]+)\"', rsp)
(rsp_pdu_len, rsp_pdu) = result.groups()
except:
raise ReaderError('Failed to parse response from modem: %s' % rsp)
# Send AT+CSIM command to the modem
# TODO: also handle +CME ERROR: <err>
rsp = self.send_at_cmd(cmd)
if len(rsp) != 2 or rsp[-1] != b'OK':
raise ReaderError('APDU transfer failed: %s' % str(rsp))
rsp = rsp[0] # Get rid of b'OK'
# TODO: make sure we have at least SW
data = rsp_pdu[:-4].decode().lower()
sw = rsp_pdu[-4:].decode().lower()
log.debug('Command response: %s, %s', data, sw)
return data, sw
# Make sure that the response has format: b'+CSIM: %d,\"%s\"'
try:
result = re.match(b'\+CSIM: (\d+),\"([0-9A-F]+)\"', rsp)
(rsp_pdu_len, rsp_pdu) = result.groups()
except:
raise ReaderError('Failed to parse response from modem: %s' % rsp)
# TODO: make sure we have at least SW
data = rsp_pdu[:-4].decode().lower()
sw = rsp_pdu[-4:].decode().lower()
log.debug('Command response: %s, %s', data, sw)
return data, sw

View File

@ -28,63 +28,64 @@ from pySim.utils import h2i, i2h
class PcscSimLink(LinkBase):
""" pySim: PCSC reader transport link."""
""" pySim: PCSC reader transport link."""
def __init__(self, reader_number:int=0, **kwargs):
super().__init__(**kwargs)
r = readers()
if reader_number >= len(r):
raise ReaderError
self._reader = r[reader_number]
self._con = self._reader.createConnection()
def __init__(self, reader_number: int = 0, **kwargs):
super().__init__(**kwargs)
r = readers()
if reader_number >= len(r):
raise ReaderError
self._reader = r[reader_number]
self._con = self._reader.createConnection()
def __del__(self):
try:
# FIXME: this causes multiple warnings in Python 3.5.3
self._con.disconnect()
except:
pass
return
def __del__(self):
try:
# FIXME: this causes multiple warnings in Python 3.5.3
self._con.disconnect()
except:
pass
return
def wait_for_card(self, timeout:int=None, newcardonly:bool=False):
cr = CardRequest(readers=[self._reader], timeout=timeout, newcardonly=newcardonly)
try:
cr.waitforcard()
except CardRequestTimeoutException:
raise NoCardError()
self.connect()
def wait_for_card(self, timeout: int = None, newcardonly: bool = False):
cr = CardRequest(readers=[self._reader],
timeout=timeout, newcardonly=newcardonly)
try:
cr.waitforcard()
except CardRequestTimeoutException:
raise NoCardError()
self.connect()
def connect(self):
try:
# To avoid leakage of resources, make sure the reader
# is disconnected
self.disconnect()
def connect(self):
try:
# To avoid leakage of resources, make sure the reader
# is disconnected
self.disconnect()
# Explicitly select T=0 communication protocol
self._con.connect(CardConnection.T0_protocol)
except CardConnectionException:
raise ProtocolError()
except NoCardException:
raise NoCardError()
# Explicitly select T=0 communication protocol
self._con.connect(CardConnection.T0_protocol)
except CardConnectionException:
raise ProtocolError()
except NoCardException:
raise NoCardError()
def get_atr(self):
return self._con.getATR()
def get_atr(self):
return self._con.getATR()
def disconnect(self):
self._con.disconnect()
def disconnect(self):
self._con.disconnect()
def reset_card(self):
self.disconnect()
self.connect()
return 1
def reset_card(self):
self.disconnect()
self.connect()
return 1
def _send_apdu_raw(self, pdu):
def _send_apdu_raw(self, pdu):
apdu = h2i(pdu)
apdu = h2i(pdu)
data, sw1, sw2 = self._con.transmit(apdu)
data, sw1, sw2 = self._con.transmit(apdu)
sw = [sw1, sw2]
sw = [sw1, sw2]
# Return value
return i2h(data), i2h(sw)
# Return value
return i2h(data), i2h(sw)

View File

@ -26,209 +26,211 @@ from pySim.utils import h2b, b2h
class SerialSimLink(LinkBase):
""" pySim: Transport Link for serial (RS232) based readers included with simcard"""
""" pySim: Transport Link for serial (RS232) based readers included with simcard"""
def __init__(self, device:str='/dev/ttyUSB0', baudrate:int=9600, rst:str='-rts',
debug:bool=False, **kwargs):
super().__init__(**kwargs)
if not os.path.exists(device):
raise ValueError("device file %s does not exist -- abort" % device)
self._sl = serial.Serial(
port = device,
parity = serial.PARITY_EVEN,
bytesize = serial.EIGHTBITS,
stopbits = serial.STOPBITS_TWO,
timeout = 1,
xonxoff = 0,
rtscts = 0,
baudrate = baudrate,
)
self._rst_pin = rst
self._debug = debug
self._atr = None
def __init__(self, device: str = '/dev/ttyUSB0', baudrate: int = 9600, rst: str = '-rts',
debug: bool = False, **kwargs):
super().__init__(**kwargs)
if not os.path.exists(device):
raise ValueError("device file %s does not exist -- abort" % device)
self._sl = serial.Serial(
port=device,
parity=serial.PARITY_EVEN,
bytesize=serial.EIGHTBITS,
stopbits=serial.STOPBITS_TWO,
timeout=1,
xonxoff=0,
rtscts=0,
baudrate=baudrate,
)
self._rst_pin = rst
self._debug = debug
self._atr = None
def __del__(self):
if (hasattr(self, "_sl")):
self._sl.close()
def __del__(self):
if (hasattr(self, "_sl")):
self._sl.close()
def wait_for_card(self, timeout=None, newcardonly=False):
# Direct try
existing = False
def wait_for_card(self, timeout=None, newcardonly=False):
# Direct try
existing = False
try:
self.reset_card()
if not newcardonly:
return
else:
existing = True
except NoCardError:
pass
try:
self.reset_card()
if not newcardonly:
return
else:
existing = True
except NoCardError:
pass
# Poll ...
mt = time.time() + timeout if timeout is not None else None
pe = 0
# Poll ...
mt = time.time() + timeout if timeout is not None else None
pe = 0
while (mt is None) or (time.time() < mt):
try:
time.sleep(0.5)
self.reset_card()
if not existing:
return
except NoCardError:
existing = False
except ProtocolError:
if existing:
existing = False
else:
# Tolerate a couple of protocol error ... can happen if
# we try when the card is 'half' inserted
pe += 1
if (pe > 2):
raise
while (mt is None) or (time.time() < mt):
try:
time.sleep(0.5)
self.reset_card()
if not existing:
return
except NoCardError:
existing = False
except ProtocolError:
if existing:
existing = False
else:
# Tolerate a couple of protocol error ... can happen if
# we try when the card is 'half' inserted
pe += 1
if (pe > 2):
raise
# Timed out ...
raise NoCardError()
# Timed out ...
raise NoCardError()
def connect(self):
self.reset_card()
def connect(self):
self.reset_card()
def get_atr(self):
return self._atr
def get_atr(self):
return self._atr
def disconnect(self):
pass # Nothing to do really ...
def disconnect(self):
pass # Nothing to do really ...
def reset_card(self):
rv = self._reset_card()
if rv == 0:
raise NoCardError()
elif rv < 0:
raise ProtocolError()
def reset_card(self):
rv = self._reset_card()
if rv == 0:
raise NoCardError()
elif rv < 0:
raise ProtocolError()
def _reset_card(self):
self._atr = None
rst_meth_map = {
'rts': self._sl.setRTS,
'dtr': self._sl.setDTR,
}
rst_val_map = { '+':0, '-':1 }
def _reset_card(self):
self._atr = None
rst_meth_map = {
'rts': self._sl.setRTS,
'dtr': self._sl.setDTR,
}
rst_val_map = {'+': 0, '-': 1}
try:
rst_meth = rst_meth_map[self._rst_pin[1:]]
rst_val = rst_val_map[self._rst_pin[0]]
except:
raise ValueError('Invalid reset pin %s' % self._rst_pin)
try:
rst_meth = rst_meth_map[self._rst_pin[1:]]
rst_val = rst_val_map[self._rst_pin[0]]
except:
raise ValueError('Invalid reset pin %s' % self._rst_pin)
rst_meth(rst_val)
time.sleep(0.1) # 100 ms
self._sl.flushInput()
rst_meth(rst_val ^ 1)
rst_meth(rst_val)
time.sleep(0.1) # 100 ms
self._sl.flushInput()
rst_meth(rst_val ^ 1)
b = self._rx_byte()
if not b:
return 0
if ord(b) != 0x3b:
return -1
self._dbg_print("TS: 0x%x Direct convention" % ord(b))
b = self._rx_byte()
if not b:
return 0
if ord(b) != 0x3b:
return -1
self._dbg_print("TS: 0x%x Direct convention" % ord(b))
while ord(b) == 0x3b:
b = self._rx_byte()
while ord(b) == 0x3b:
b = self._rx_byte()
if not b:
return -1
t0 = ord(b)
self._dbg_print("T0: 0x%x" % t0)
self._atr = [0x3b, ord(b)]
if not b:
return -1
t0 = ord(b)
self._dbg_print("T0: 0x%x" % t0)
self._atr = [0x3b, ord(b)]
for i in range(4):
if t0 & (0x10 << i):
b = self._rx_byte()
self._atr.append(ord(b))
self._dbg_print("T%si = %x" % (chr(ord('A')+i), ord(b)))
for i in range(4):
if t0 & (0x10 << i):
b = self._rx_byte()
self._atr.append(ord(b))
self._dbg_print("T%si = %x" % (chr(ord('A')+i), ord(b)))
for i in range(0, t0 & 0xf):
b = self._rx_byte()
self._atr.append(ord(b))
self._dbg_print("Historical = %x" % ord(b))
for i in range(0, t0 & 0xf):
b = self._rx_byte()
self._atr.append(ord(b))
self._dbg_print("Historical = %x" % ord(b))
while True:
x = self._rx_byte()
if not x:
break
self._atr.append(ord(x))
self._dbg_print("Extra: %x" % ord(x))
while True:
x = self._rx_byte()
if not x:
break
self._atr.append(ord(x))
self._dbg_print("Extra: %x" % ord(x))
return 1
return 1
def _dbg_print(self, s):
if self._debug:
print(s)
def _dbg_print(self, s):
if self._debug:
print(s)
def _tx_byte(self, b):
self._sl.write(b)
r = self._sl.read()
if r != b: # TX and RX are tied, so we must clear the echo
raise ProtocolError("Bad echo value. Expected %02x, got %s)" % (ord(b), '%02x'%ord(r) if r else '(nil)'))
def _tx_byte(self, b):
self._sl.write(b)
r = self._sl.read()
if r != b: # TX and RX are tied, so we must clear the echo
raise ProtocolError("Bad echo value. Expected %02x, got %s)" % (
ord(b), '%02x' % ord(r) if r else '(nil)'))
def _tx_string(self, s):
"""This is only safe if it's guaranteed the card won't send any data
during the time of tx of the string !!!"""
self._sl.write(s)
r = self._sl.read(len(s))
if r != s: # TX and RX are tied, so we must clear the echo
raise ProtocolError("Bad echo value (Expected: %s, got %s)" % (b2h(s), b2h(r)))
def _tx_string(self, s):
"""This is only safe if it's guaranteed the card won't send any data
during the time of tx of the string !!!"""
self._sl.write(s)
r = self._sl.read(len(s))
if r != s: # TX and RX are tied, so we must clear the echo
raise ProtocolError(
"Bad echo value (Expected: %s, got %s)" % (b2h(s), b2h(r)))
def _rx_byte(self):
return self._sl.read()
def _rx_byte(self):
return self._sl.read()
def _send_apdu_raw(self, pdu):
def _send_apdu_raw(self, pdu):
pdu = h2b(pdu)
data_len = pdu[4] # P3
pdu = h2b(pdu)
data_len = pdu[4] # P3
# Send first CLASS,INS,P1,P2,P3
self._tx_string(pdu[0:5])
# Send first CLASS,INS,P1,P2,P3
self._tx_string(pdu[0:5])
# Wait ack which can be
# - INS: Command acked -> go ahead
# - 0x60: NULL, just wait some more
# - SW1: The card can apparently proceed ...
while True:
b = self._rx_byte()
if ord(b) == pdu[1]:
break
elif b != '\x60':
# Ok, it 'could' be SW1
sw1 = b
sw2 = self._rx_byte()
nil = self._rx_byte()
if (sw2 and not nil):
return '', b2h(sw1+sw2)
# Wait ack which can be
# - INS: Command acked -> go ahead
# - 0x60: NULL, just wait some more
# - SW1: The card can apparently proceed ...
while True:
b = self._rx_byte()
if ord(b) == pdu[1]:
break
elif b != '\x60':
# Ok, it 'could' be SW1
sw1 = b
sw2 = self._rx_byte()
nil = self._rx_byte()
if (sw2 and not nil):
return '', b2h(sw1+sw2)
raise ProtocolError()
raise ProtocolError()
# Send data (if any)
if len(pdu) > 5:
self._tx_string(pdu[5:])
# Send data (if any)
if len(pdu) > 5:
self._tx_string(pdu[5:])
# Receive data (including SW !)
# length = [P3 - tx_data (=len(pdu)-len(hdr)) + 2 (SW1//2) ]
to_recv = data_len - len(pdu) + 5 + 2
# Receive data (including SW !)
# length = [P3 - tx_data (=len(pdu)-len(hdr)) + 2 (SW1//2) ]
to_recv = data_len - len(pdu) + 5 + 2
data = bytes(0)
while (len(data) < to_recv):
b = self._rx_byte()
if (to_recv == 2) and (b == '\x60'): # Ignore NIL if we have no RX data (hack ?)
continue
if not b:
break
data += b
data = bytes(0)
while (len(data) < to_recv):
b = self._rx_byte()
if (to_recv == 2) and (b == '\x60'): # Ignore NIL if we have no RX data (hack ?)
continue
if not b:
break
data += b
# Split datafield from SW
if len(data) < 2:
return None, None
sw = data[-2:]
data = data[0:-2]
# Split datafield from SW
if len(data) < 2:
return None, None
sw = data[-2:]
data = data[0:-2]
# Return value
return b2h(data), b2h(sw)
# Return value
return b2h(data), b2h(sw)

View File

@ -76,7 +76,7 @@ ts_102_22x_cmdset = CardCommandSet('TS 102 22x', [
CardCommand('TERMINATE EF', 0xE8, ['0X', '4X']),
CardCommand('TERMINATE CARD USAGE', 0xFE, ['0X', '4X']),
CardCommand('RESIZE FILE', 0xD4, ['8X', 'CX']),
])
])
FCP_TLV_MAP = {
@ -92,7 +92,7 @@ FCP_TLV_MAP = {
'80': 'file_size',
'81': 'total_file_size',
'88': 'short_file_id',
}
}
# ETSI TS 102 221 11.1.1.4.6
FCP_Proprietary_TLV_MAP = {
@ -107,9 +107,11 @@ FCP_Proprietary_TLV_MAP = {
'88': 'specific_uicc_env_cond',
'89': 'p2p_cat_secured_apdu',
# Additional private TLV objects (bits b7 and b8 of the first byte of the tag set to '1')
}
}
# ETSI TS 102 221 11.1.1.4.3
def interpret_file_descriptor(in_hex):
in_bin = h2b(in_hex)
out = {}
@ -123,7 +125,7 @@ def interpret_file_descriptor(in_hex):
1: 'transparent',
2: 'linear_fixed',
6: 'cyclic',
0x39: 'ber_tlv',
0x39: 'ber_tlv',
}
fdb = in_bin[0]
ftype = (fdb >> 3) & 7
@ -140,6 +142,8 @@ def interpret_file_descriptor(in_hex):
return out
# ETSI TS 102 221 11.1.1.4.9
def interpret_life_cycle_sts_int(in_hex):
lcsi = int(in_hex, 16)
if lcsi == 0x00:
@ -157,35 +161,40 @@ def interpret_life_cycle_sts_int(in_hex):
else:
return in_hex
# ETSI TS 102 221 11.1.1.4.10
FCP_Pin_Status_TLV_MAP = {
'90': 'ps_do',
'95': 'usage_qualifier',
'83': 'key_reference',
}
}
def interpret_ps_templ_do(in_hex):
# cannot use the 'TLV' parser due to repeating tags
#psdo_tlv = TLV(FCP_Pin_Status_TLV_MAP)
#return psdo_tlv.parse(in_hex)
# return psdo_tlv.parse(in_hex)
return in_hex
# 'interpreter' functions for each tag
FCP_interpreter_map = {
'80': lambda x: int(x, 16),
'82': interpret_file_descriptor,
'8A': interpret_life_cycle_sts_int,
'C6': interpret_ps_templ_do,
}
}
FCP_prorietary_interpreter_map = {
'83': lambda x: int(x, 16),
}
}
# pytlv unfortunately doesn't have a setting using which we can make it
# accept unknown tags. It also doesn't raise a specific exception type but
# just the generic ValueError, so we cannot ignore those either. Instead,
# we insert a dict entry for every possible proprietary tag permitted
def fixup_fcp_proprietary_tlv_map(tlv_map):
if 'D0' in tlv_map:
return
@ -193,7 +202,7 @@ def fixup_fcp_proprietary_tlv_map(tlv_map):
i_hex = i2h([i]).upper()
tlv_map[i_hex] = 'proprietary_' + i_hex
# Other non-standard TLV objects found on some cards
tlv_map['9B'] = 'target_ef' # for sysmoUSIM-SJS1
tlv_map['9B'] = 'target_ef' # for sysmoUSIM-SJS1
def tlv_key_replace(inmap, indata):
@ -204,6 +213,7 @@ def tlv_key_replace(inmap, indata):
return key
return {newkey(inmap, d[0]): d[1] for d in indata.items()}
def tlv_val_interpret(inmap, indata):
def newval(inmap, key, val):
if key in inmap:
@ -214,11 +224,12 @@ def tlv_val_interpret(inmap, indata):
# ETSI TS 102 221 Section 9.2.7 + ISO7816-4 9.3.3/9.3.4
class _AM_DO_DF(DataObject):
def __init__(self):
super().__init__('access_mode', 'Access Mode', tag=0x80)
def from_bytes(self, do:bytes):
def from_bytes(self, do: bytes):
res = []
if len(do) != 1:
raise ValueError("We only support single-byte AMF inside AM-DO")
@ -262,10 +273,11 @@ class _AM_DO_DF(DataObject):
class _AM_DO_EF(DataObject):
"""ISO7816-4 9.3.2 Table 18 + 9.3.3.1 Table 31"""
def __init__(self):
super().__init__('access_mode', 'Access Mode', tag=0x80)
def from_bytes(self, do:bytes):
def from_bytes(self, do: bytes):
res = []
if len(do) != 1:
raise ValueError("We only support single-byte AMF inside AM-DO")
@ -306,12 +318,14 @@ class _AM_DO_EF(DataObject):
val |= 0x01
return val.to_bytes(1, 'big')
class _AM_DO_CHDR(DataObject):
"""Command Header Access Mode DO according to ISO 7816-4 Table 32."""
def __init__(self, tag):
super().__init__('command_header', 'Command Header Description', tag=tag)
def from_bytes(self, do:bytes):
def from_bytes(self, do: bytes):
res = {}
i = 0
if self.tag & 0x08:
@ -353,11 +367,12 @@ class _AM_DO_CHDR(DataObject):
res.append(self.decoded['P2'])
return res
AM_DO_CHDR = DataObjectChoice('am_do_chdr', members=[
_AM_DO_CHDR(0x81), _AM_DO_CHDR(0x82), _AM_DO_CHDR(0x83), _AM_DO_CHDR(0x84),
_AM_DO_CHDR(0x85), _AM_DO_CHDR(0x86), _AM_DO_CHDR(0x87), _AM_DO_CHDR(0x88),
_AM_DO_CHDR(0x89), _AM_DO_CHDR(0x8a), _AM_DO_CHDR(0x8b), _AM_DO_CHDR(0x8c),
_AM_DO_CHDR(0x8d), _AM_DO_CHDR(0x8e), _AM_DO_CHDR(0x8f)])
_AM_DO_CHDR(0x81), _AM_DO_CHDR(0x82), _AM_DO_CHDR(0x83), _AM_DO_CHDR(0x84),
_AM_DO_CHDR(0x85), _AM_DO_CHDR(0x86), _AM_DO_CHDR(0x87), _AM_DO_CHDR(0x88),
_AM_DO_CHDR(0x89), _AM_DO_CHDR(0x8a), _AM_DO_CHDR(0x8b), _AM_DO_CHDR(0x8c),
_AM_DO_CHDR(0x8d), _AM_DO_CHDR(0x8e), _AM_DO_CHDR(0x8f)])
AM_DO_DF = AM_DO_CHDR | _AM_DO_DF()
AM_DO_EF = AM_DO_CHDR | _AM_DO_EF()
@ -393,12 +408,15 @@ pin_names = bidict({
0x8c: 'ADM8',
0x8d: 'ADM9',
0x8e: 'ADM10',
})
})
class CRT_DO(DataObject):
"""Control Reference Template as per TS 102 221 9.5.1"""
def __init__(self):
super().__init__('control_reference_template', 'Control Reference Template', tag=0xA4)
super().__init__('control_reference_template',
'Control Reference Template', tag=0xA4)
def from_bytes(self, do: bytes):
"""Decode a Control Reference Template DO."""
@ -407,7 +425,8 @@ class CRT_DO(DataObject):
if do[0] != 0x83 or do[1] != 0x01:
raise ValueError('Unsupported Key Ref Tag or Len in CRT DO %s', do)
if do[3:] != b'\x95\x01\x08':
raise ValueError('Unsupported Usage Qualifier Tag or Len in CRT DO %s', do)
raise ValueError(
'Unsupported Usage Qualifier Tag or Len in CRT DO %s', do)
self.encoded = do[0:6]
self.decoded = pin_names[do[2]]
return do[6:]
@ -417,11 +436,13 @@ class CRT_DO(DataObject):
return b'\x83\x01' + pin.to_bytes(1, 'big') + b'\x95\x01\x08'
# ISO7816-4 9.3.3 Table 33
class SecCondByte_DO(DataObject):
def __init__(self, tag=0x9d):
super().__init__('security_condition_byte', tag=tag)
def from_bytes(self, binary:bytes):
def from_bytes(self, binary: bytes):
if len(binary) != 1:
raise ValueError
inb = binary[0]
@ -440,7 +461,7 @@ class SecCondByte_DO(DataObject):
res.append('external_auth')
if inb & 0x10:
res.append('user_auth')
rd = {'mode': cond }
rd = {'mode': cond}
if len(res):
rd['conditions'] = res
self.decoded = rd
@ -470,39 +491,47 @@ class SecCondByte_DO(DataObject):
raise ValueError('Unknown condition %s' % c)
return res.to_bytes(1, 'big')
Always_DO = TL0_DataObject('always', 'Always', 0x90)
Never_DO = TL0_DataObject('never', 'Never', 0x97)
class Nested_DO(DataObject):
"""A DO that nests another DO/Choice/Sequence"""
def __init__(self, name, tag, choice):
super().__init__(name, tag=tag)
self.children = choice
def from_bytes(self, binary:bytes) -> list:
def from_bytes(self, binary: bytes) -> list:
remainder = binary
self.decoded = []
while remainder:
rc, remainder = self.children.decode(remainder)
self.decoded.append(rc)
return self.decoded
def to_bytes(self) -> bytes:
encoded = [self.children.encode(d) for d in self.decoded]
return b''.join(encoded)
OR_Template = DataObjectChoice('or_template', 'OR-Template',
members=[Always_DO, Never_DO, SecCondByte_DO(), SecCondByte_DO(0x9e), CRT_DO()])
OR_DO = Nested_DO('or', 0xa0, OR_Template)
AND_Template = DataObjectChoice('and_template', 'AND-Template',
members=[Always_DO, Never_DO, SecCondByte_DO(), SecCondByte_DO(0x9e), CRT_DO()])
members=[Always_DO, Never_DO, SecCondByte_DO(), SecCondByte_DO(0x9e), CRT_DO()])
AND_DO = Nested_DO('and', 0xa7, AND_Template)
NOT_Template = DataObjectChoice('not_template', 'NOT-Template',
members=[Always_DO, Never_DO, SecCondByte_DO(), SecCondByte_DO(0x9e), CRT_DO()])
members=[Always_DO, Never_DO, SecCondByte_DO(), SecCondByte_DO(0x9e), CRT_DO()])
NOT_DO = Nested_DO('not', 0xaf, NOT_Template)
SC_DO = DataObjectChoice('security_condition', 'Security Condition',
members=[Always_DO, Never_DO, SecCondByte_DO(), SecCondByte_DO(0x9e), CRT_DO(),
OR_DO, AND_DO, NOT_DO])
# TS 102 221 Section 13.1
class EF_DIR(LinFixedEF):
class ApplicationLabel(BER_TLV_IE, tag=0x50):
# TODO: UCS-2 coding option as per Annex A of TS 102 221
@ -518,13 +547,15 @@ class EF_DIR(LinFixedEF):
pass
def __init__(self, fid='2f00', sfid=0x1e, name='EF.DIR', desc='Application Directory'):
super().__init__(fid, sfid=sfid, name=name, desc=desc, rec_len={5,54})
super().__init__(fid, sfid=sfid, name=name, desc=desc, rec_len={5, 54})
self._tlv = EF_DIR.ApplicationTemplate
# TS 102 221 Section 13.2
class EF_ICCID(TransparentEF):
def __init__(self, fid='2fe2', sfid=0x02, name='EF.ICCID', desc='ICC Identification'):
super().__init__(fid, sfid=sfid, name=name, desc=desc, size={10,10})
super().__init__(fid, sfid=sfid, name=name, desc=desc, size={10, 10})
def _decode_hex(self, raw_hex):
return {'iccid': dec_iccid(raw_hex)}
@ -533,14 +564,19 @@ class EF_ICCID(TransparentEF):
return enc_iccid(abstract['iccid'])
# TS 102 221 Section 13.3
class EF_PL(TransRecEF):
def __init__(self, fid='2f05', sfid=0x05, name='EF.PL', desc='Preferred Languages'):
super().__init__(fid, sfid=sfid, name=name, desc=desc, rec_len=2, size={2,None})
super().__init__(fid, sfid=sfid, name=name,
desc=desc, rec_len=2, size={2, None})
def _decode_record_bin(self, bin_data):
if bin_data == b'\xff\xff':
return None
else:
return bin_data.decode('ascii')
def _encode_record_bin(self, in_json):
if in_json is None:
return b'\xff\xff'
@ -556,7 +592,7 @@ class EF_ARR(LinFixedEF):
self.shell_commands += [self.AddlShellCommands()]
@staticmethod
def flatten(inp:list):
def flatten(inp: list):
"""Flatten the somewhat deep/complex/nested data returned from decoder."""
def sc_abbreviate(sc):
if 'always' in sc:
@ -584,7 +620,7 @@ class EF_ARR(LinFixedEF):
cla = None
cmd = ts_102_22x_cmdset.lookup(ins, cla)
if cmd:
name = cmd.name.lower().replace(' ','_')
name = cmd.name.lower().replace(' ', '_')
by_mode[name] = sc_abbr
else:
raise ValueError
@ -594,7 +630,7 @@ class EF_ARR(LinFixedEF):
def _decode_record_bin(self, raw_bin_data):
# we can only guess if we should decode for EF or DF here :(
arr_seq = DataObjectSequence('arr', sequence = [AM_DO_EF, SC_DO])
arr_seq = DataObjectSequence('arr', sequence=[AM_DO_EF, SC_DO])
dec = arr_seq.decode_multi(raw_bin_data)
# we cannot pass the result through flatten() here, as we don't have a related
# 'un-flattening' decoder, and hence would be unable to encode :(
@ -628,15 +664,18 @@ class EF_ARR(LinFixedEF):
# TS 102 221 Section 13.6
class EF_UMPC(TransparentEF):
def __init__(self, fid='2f08', sfid=0x08, name='EF.UMPC', desc='UICC Maximum Power Consumption'):
super().__init__(fid, sfid=sfid, name=name, desc=desc, size={5,5})
addl_info = FlagsEnum(Byte, req_inc_idle_current=1, support_uicc_suspend=2)
self._construct = Struct('max_current_mA'/Int8ub, 't_op_s'/Int8ub, 'addl_info'/addl_info)
super().__init__(fid, sfid=sfid, name=name, desc=desc, size={5, 5})
addl_info = FlagsEnum(Byte, req_inc_idle_current=1,
support_uicc_suspend=2)
self._construct = Struct(
'max_current_mA'/Int8ub, 't_op_s'/Int8ub, 'addl_info'/addl_info)
class CardProfileUICC(CardProfile):
ORDER = 1
def __init__(self, name = 'UICC'):
def __init__(self, name='UICC'):
files = [
EF_DIR(),
EF_ICCID(),
@ -646,78 +685,79 @@ class CardProfileUICC(CardProfile):
EF_UMPC(),
]
sw = {
'Normal': {
'9000': 'Normal ending of the command',
'91xx': 'Normal ending of the command, with extra information from the proactive UICC containing a command for the terminal',
'92xx': 'Normal ending of the command, with extra information concerning an ongoing data transfer session',
'Normal': {
'9000': 'Normal ending of the command',
'91xx': 'Normal ending of the command, with extra information from the proactive UICC containing a command for the terminal',
'92xx': 'Normal ending of the command, with extra information concerning an ongoing data transfer session',
},
'Postponed processing': {
'9300': 'SIM Application Toolkit is busy. Command cannot be executed at present, further normal commands are allowed',
'Postponed processing': {
'9300': 'SIM Application Toolkit is busy. Command cannot be executed at present, further normal commands are allowed',
},
'Warnings': {
'6200': 'No information given, state of non-volatile memory unchanged',
'6281': 'Part of returned data may be corrupted',
'6282': 'End of file/record reached before reading Le bytes or unsuccessful search',
'6283': 'Selected file invalidated',
'6284': 'Selected file in termination state',
'62f1': 'More data available',
'62f2': 'More data available and proactive command pending',
'62f3': 'Response data available',
'63f1': 'More data expected',
'63f2': 'More data expected and proactive command pending',
'63cx': 'Command successful but after using an internal update retry routine X times',
'Warnings': {
'6200': 'No information given, state of non-volatile memory unchanged',
'6281': 'Part of returned data may be corrupted',
'6282': 'End of file/record reached before reading Le bytes or unsuccessful search',
'6283': 'Selected file invalidated',
'6284': 'Selected file in termination state',
'62f1': 'More data available',
'62f2': 'More data available and proactive command pending',
'62f3': 'Response data available',
'63f1': 'More data expected',
'63f2': 'More data expected and proactive command pending',
'63cx': 'Command successful but after using an internal update retry routine X times',
},
'Execution errors': {
'6400': 'No information given, state of non-volatile memory unchanged',
'6500': 'No information given, state of non-volatile memory changed',
'6581': 'Memory problem',
'Execution errors': {
'6400': 'No information given, state of non-volatile memory unchanged',
'6500': 'No information given, state of non-volatile memory changed',
'6581': 'Memory problem',
},
'Checking errors': {
'6700': 'Wrong length',
'67xx': 'The interpretation of this status word is command dependent',
'6b00': 'Wrong parameter(s) P1-P2',
'6d00': 'Instruction code not supported or invalid',
'6e00': 'Class not supported',
'6f00': 'Technical problem, no precise diagnosis',
'6fxx': 'The interpretation of this status word is command dependent',
'Checking errors': {
'6700': 'Wrong length',
'67xx': 'The interpretation of this status word is command dependent',
'6b00': 'Wrong parameter(s) P1-P2',
'6d00': 'Instruction code not supported or invalid',
'6e00': 'Class not supported',
'6f00': 'Technical problem, no precise diagnosis',
'6fxx': 'The interpretation of this status word is command dependent',
},
'Functions in CLA not supported': {
'6800': 'No information given',
'6881': 'Logical channel not supported',
'6882': 'Secure messaging not supported',
'Functions in CLA not supported': {
'6800': 'No information given',
'6881': 'Logical channel not supported',
'6882': 'Secure messaging not supported',
},
'Command not allowed': {
'6900': 'No information given',
'6981': 'Command incompatible with file structure',
'6982': 'Security status not satisfied',
'6983': 'Authentication/PIN method blocked',
'6984': 'Referenced data invalidated',
'6985': 'Conditions of use not satisfied',
'6986': 'Command not allowed (no EF selected)',
'6989': 'Command not allowed - secure channel - security not satisfied',
'Command not allowed': {
'6900': 'No information given',
'6981': 'Command incompatible with file structure',
'6982': 'Security status not satisfied',
'6983': 'Authentication/PIN method blocked',
'6984': 'Referenced data invalidated',
'6985': 'Conditions of use not satisfied',
'6986': 'Command not allowed (no EF selected)',
'6989': 'Command not allowed - secure channel - security not satisfied',
},
'Wrong parameters': {
'6a80': 'Incorrect parameters in the data field',
'6a81': 'Function not supported',
'6a82': 'File not found',
'6a83': 'Record not found',
'6a84': 'Not enough memory space',
'6a86': 'Incorrect parameters P1 to P2',
'6a87': 'Lc inconsistent with P1 to P2',
'6a88': 'Referenced data not found',
'Wrong parameters': {
'6a80': 'Incorrect parameters in the data field',
'6a81': 'Function not supported',
'6a82': 'File not found',
'6a83': 'Record not found',
'6a84': 'Not enough memory space',
'6a86': 'Incorrect parameters P1 to P2',
'6a87': 'Lc inconsistent with P1 to P2',
'6a88': 'Referenced data not found',
},
'Application errors': {
'9850': 'INCREASE cannot be performed, max value reached',
'9862': 'Authentication error, application specific',
'9863': 'Security session or association expired',
'9864': 'Minimum UICC suspension time is too long',
'Application errors': {
'9850': 'INCREASE cannot be performed, max value reached',
'9862': 'Authentication error, application specific',
'9863': 'Security session or association expired',
'9864': 'Minimum UICC suspension time is too long',
},
}
}
super().__init__(name, desc='ETSI TS 102 221', cla="00", sel_ctrl="0004", files_in_mf=files, sw=sw)
super().__init__(name, desc='ETSI TS 102 221', cla="00",
sel_ctrl="0004", files_in_mf=files, sw=sw)
@staticmethod
def decode_select_response(resp_hex:str) -> object:
def decode_select_response(resp_hex: str) -> object:
"""ETSI TS 102 221 Section 11.1.1.3"""
fixup_fcp_proprietary_tlv_map(FCP_Proprietary_TLV_MAP)
resp_hex = resp_hex.upper()
@ -738,9 +778,10 @@ class CardProfileUICC(CardProfile):
return tlv_key_replace(FCP_TLV_MAP, r)
@staticmethod
def match_with_card(scc:SimCardCommands) -> bool:
def match_with_card(scc: SimCardCommands) -> bool:
return match_uicc(scc)
class CardProfileUICCSIM(CardProfileUICC):
"""Same as above, but including 2G SIM support"""
@ -754,5 +795,5 @@ class CardProfileUICCSIM(CardProfileUICC):
self.files_in_mf.append(DF_GSM())
@staticmethod
def match_with_card(scc:SimCardCommands) -> bool:
def match_with_card(scc: SimCardCommands) -> bool:
return match_uicc(scc) and match_sim(scc)

File diff suppressed because it is too large Load Diff

View File

@ -32,129 +32,160 @@ from pySim.ts_102_221 import EF_ARR
# Mapping between ISIM Service Number and its description
EF_IST_map = {
1: 'P-CSCF address',
2: 'Generic Bootstrapping Architecture (GBA)',
3: 'HTTP Digest',
4: 'GBA-based Local Key Establishment Mechanism',
5: 'Support of P-CSCF discovery for IMS Local Break Out',
6: 'Short Message Storage (SMS)',
7: 'Short Message Status Reports (SMSR)',
8: 'Support for SM-over-IP including data download via SMS-PP as defined in TS 31.111 [31]',
9: 'Communication Control for IMS by ISIM',
10: 'Support of UICC access to IMS',
11: 'URI support by UICC',
12: 'Media Type support',
13: 'IMS call disconnection cause',
14: 'URI support for MO SHORT MESSAGE CONTROL',
15: 'MCPTT',
16: 'URI support for SMS-PP DOWNLOAD as defined in 3GPP TS 31.111 [31]',
17: 'From Preferred',
18: 'IMS configuration data',
19: 'XCAP Configuration Data',
20: 'WebRTC URI',
21: 'MuD and MiD configuration data',
1: 'P-CSCF address',
2: 'Generic Bootstrapping Architecture (GBA)',
3: 'HTTP Digest',
4: 'GBA-based Local Key Establishment Mechanism',
5: 'Support of P-CSCF discovery for IMS Local Break Out',
6: 'Short Message Storage (SMS)',
7: 'Short Message Status Reports (SMSR)',
8: 'Support for SM-over-IP including data download via SMS-PP as defined in TS 31.111 [31]',
9: 'Communication Control for IMS by ISIM',
10: 'Support of UICC access to IMS',
11: 'URI support by UICC',
12: 'Media Type support',
13: 'IMS call disconnection cause',
14: 'URI support for MO SHORT MESSAGE CONTROL',
15: 'MCPTT',
16: 'URI support for SMS-PP DOWNLOAD as defined in 3GPP TS 31.111 [31]',
17: 'From Preferred',
18: 'IMS configuration data',
19: 'XCAP Configuration Data',
20: 'WebRTC URI',
21: 'MuD and MiD configuration data',
}
EF_ISIM_ADF_map = {
'IST': '6F07',
'IMPI': '6F02',
'DOMAIN': '6F03',
'IMPU': '6F04',
'AD': '6FAD',
'ARR': '6F06',
'PCSCF': '6F09',
'GBAP': '6FD5',
'GBANL': '6FD7',
'NAFKCA': '6FDD',
'UICCIARI': '6FE7',
'SMS': '6F3C',
'SMSS': '6F43',
'SMSR': '6F47',
'SMSP': '6F42',
'FromPreferred': '6FF7',
'IMSConfigData': '6FF8',
'XCAPConfigData': '6FFC',
'WebRTCURI': '6FFA'
'IST': '6F07',
'IMPI': '6F02',
'DOMAIN': '6F03',
'IMPU': '6F04',
'AD': '6FAD',
'ARR': '6F06',
'PCSCF': '6F09',
'GBAP': '6FD5',
'GBANL': '6FD7',
'NAFKCA': '6FDD',
'UICCIARI': '6FE7',
'SMS': '6F3C',
'SMSS': '6F43',
'SMSR': '6F47',
'SMSP': '6F42',
'FromPreferred': '6FF7',
'IMSConfigData': '6FF8',
'XCAPConfigData': '6FFC',
'WebRTCURI': '6FFA'
}
# TS 31.103 Section 4.2.2
class EF_IMPI(TransparentEF):
class nai(BER_TLV_IE, tag=0x80):
_construct = GreedyString("utf8")
def __init__(self, fid='6f02', sfid=0x02, name='EF.IMPI', desc='IMS private user identity'):
super().__init__(fid=fid, sfid=sfid, name=name, desc=desc)
self._tlv = EF_IMPI.nai
# TS 31.103 Section 4.2.3
class EF_DOMAIN(TransparentEF):
class domain(BER_TLV_IE, tag=0x80):
_construct = GreedyString("utf8")
def __init__(self, fid='6f05', sfid=0x05, name='EF.DOMAIN', desc='Home Network Domain Name'):
super().__init__(fid=fid, sfid=sfid, name=name, desc=desc)
self._tlv = EF_DOMAIN.domain
# TS 31.103 Section 4.2.4
class EF_IMPU(LinFixedEF):
class impu(BER_TLV_IE, tag=0x80):
_construct = GreedyString("utf8")
def __init__(self, fid='6f04', sfid=0x04, name='EF.IMPU', desc='IMS public user identity'):
super().__init__(fid=fid, sfid=sfid, name=name, desc=desc)
self._tlv = EF_IMPU.impu
# TS 31.103 Section 4.2.8
class EF_PCSCF(LinFixedEF):
def __init__(self, fid='6f09', sfid=None, name='EF.P-CSCF', desc='P-CSCF Address'):
super().__init__(fid=fid, sfid=sfid, name=name, desc=desc)
def _decode_record_hex(self, raw_hex):
addr, addr_type = dec_addr_tlv(raw_hex)
return {"addr": addr, "addr_type": addr_type}
def _encode_record_hex(self, json_in):
addr = json_in['addr']
addr_type = json_in['addr_type']
return enc_addr_tlv(addr, addr_type)
# TS 31.103 Section 4.2.9
class EF_GBABP(TransparentEF):
def __init__(self, fid='6fd5', sfid=None, name='EF.GBABP', desc='GBA Bootstrapping'):
super().__init__(fid=fid, sfid=sfid, name=name, desc=desc)
# TS 31.103 Section 4.2.10
class EF_GBANL(LinFixedEF):
def __init__(self, fid='6fd7', sfid=None, name='EF.GBANL', desc='GBA NAF List'):
super().__init__(fid=fid, sfid=sfid, name=name, desc=desc)
# TS 31.103 Section 4.2.11
class EF_NAFKCA(LinFixedEF):
def __init__(self, fid='6fdd', sfid=None, name='EF.NAFKCA', desc='NAF Key Centre Address'):
super().__init__(fid=fid, sfid=sfid, name=name, desc=desc)
# TS 31.103 Section 4.2.16
class EF_UICCIARI(LinFixedEF):
class iari(BER_TLV_IE, tag=0x80):
_construct = GreedyString("utf8")
def __init__(self, fid='6fe7', sfid=None, name='EF.UICCIARI', desc='UICC IARI'):
super().__init__(fid=fid, sfid=sfid, name=name, desc=desc)
self._tlv = EF_UICCIARI.iari
# TS 31.103 Section 4.2.18
class EF_IMSConfigData(BerTlvEF):
def __init__(self, fid='6ff8', sfid=None, name='EF.IMSConfigData', desc='IMS Configuration Data'):
super().__init__(fid=fid, sfid=sfid, name=name, desc=desc)
# TS 31.103 Section 4.2.19
class EF_XCAPConfigData(BerTlvEF):
def __init__(self, fid='6ffc', sfid=None, name='EF.XCAPConfigData', desc='XCAP Configuration Data'):
super().__init__(fid=fid, sfid=sfid, name=name, desc=desc)
# TS 31.103 Section 4.2.20
class EF_WebRTCURI(TransparentEF):
class uri(BER_TLV_IE, tag=0x80):
_construct = GreedyString("utf8")
def __init__(self, fid='6ffa', sfid=None, name='EF.WebRTCURI', desc='WebRTC URI'):
super().__init__(fid=fid, sfid=sfid, name=name, desc=desc)
self._tlv = EF_WebRTCURI.uri
# TS 31.103 Section 4.2.21
class EF_MuDMiDConfigData(BerTlvEF):
def __init__(self, fid='6ffe', sfid=None, name='EF.MuDMiDConfigData',
desc='MuD and MiD Configuration Data'):
@ -172,7 +203,8 @@ class ADF_ISIM(CardADF):
EF_IMPU(),
EF_AD(),
EF_ARR('6f06', 0x06),
EF_UServiceTable('6f07', 0x07, 'EF.IST', 'ISIM Service Table', {1,None}, EF_IST_map),
EF_UServiceTable('6f07', 0x07, 'EF.IST',
'ISIM Service Table', {1, None}, EF_IST_map),
EF_PCSCF(),
EF_GBABP(),
EF_GBANL(),
@ -187,7 +219,7 @@ class ADF_ISIM(CardADF):
EF_XCAPConfigData(),
EF_WebRTCURI(),
EF_MuDMiDConfigData(),
]
]
self.add_files(files)
# add those commands to the general commands of a TransparentEF
self.shell_commands += [ADF_USIM.AddlShellCommands()]
@ -195,6 +227,7 @@ class ADF_ISIM(CardADF):
def decode_select_response(self, data_hex):
return pySim.ts_102_221.CardProfileUICC.decode_select_response(data_hex)
# TS 31.103 Section 7.1
sw_isim = {
'Security management': {
@ -203,6 +236,7 @@ sw_isim = {
}
}
class CardApplicationISIM(CardApplication):
def __init__(self):
super().__init__('ISIM', adf=ADF_ISIM(), sw=sw_isim)
super().__init__('ISIM', adf=ADF_ISIM(), sw=sw_isim)

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff