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 from pySim.utils import h2s, format_ePDGSelection
option_parser = argparse.ArgumentParser(prog='pySim-read', option_parser = argparse.ArgumentParser(prog='pySim-read',
description='Legacy tool for reading some parts of a SIM card', description='Legacy tool for reading some parts of a SIM card',
formatter_class=argparse.ArgumentDefaultsHelpFormatter) formatter_class=argparse.ArgumentDefaultsHelpFormatter)
argparse_add_reader_args(option_parser) 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__': if __name__ == '__main__':
# Parse options # Parse options
opts = option_parser.parse_args() opts = option_parser.parse_args()
# Init card reader driver # Init card reader driver
sl = init_reader(opts) sl = init_reader(opts)
if sl is None: if sl is None:
exit(1) exit(1)
# Create command layer # Create command layer
scc = SimCardCommands(transport=sl) scc = SimCardCommands(transport=sl)
# Wait for SIM card # Wait for SIM card
sl.wait_for_card() sl.wait_for_card()
# Assuming UICC SIM # Assuming UICC SIM
scc.cla_byte = "00" scc.cla_byte = "00"
scc.sel_ctrl = "0004" scc.sel_ctrl = "0004"
# Testing for Classic SIM or UICC # Testing for Classic SIM or UICC
(res, sw) = sl.send_apdu(scc.cla_byte + "a4" + scc.sel_ctrl + "02" + "3f00") (res, sw) = sl.send_apdu(scc.cla_byte + "a4" + scc.sel_ctrl + "02" + "3f00")
if sw == '6e00': if sw == '6e00':
# Just a Classic SIM # Just a Classic SIM
scc.cla_byte = "a0" scc.cla_byte = "a0"
scc.sel_ctrl = "0000" scc.sel_ctrl = "0000"
# Read the card # Read the card
print("Reading ...") print("Reading ...")
# Initialize Card object by auto detecting the card # Initialize Card object by auto detecting the card
card = card_detect("auto", scc) or SimCard(scc) card = card_detect("auto", scc) or SimCard(scc)
# Read all AIDs on the UICC # Read all AIDs on the UICC
card.read_aids() card.read_aids()
# EF.ICCID # EF.ICCID
(res, sw) = card.read_iccid() (res, sw) = card.read_iccid()
if sw == '9000': if sw == '9000':
print("ICCID: %s" % (res,)) print("ICCID: %s" % (res,))
else: else:
print("ICCID: Can't read, response code = %s" % (sw,)) print("ICCID: Can't read, response code = %s" % (sw,))
# EF.IMSI # EF.IMSI
(res, sw) = card.read_imsi() (res, sw) = card.read_imsi()
if sw == '9000': if sw == '9000':
print("IMSI: %s" % (res,)) print("IMSI: %s" % (res,))
else: else:
print("IMSI: Can't read, response code = %s" % (sw,)) print("IMSI: Can't read, response code = %s" % (sw,))
# EF.GID1 # EF.GID1
try: try:
(res, sw) = card.read_gid1() (res, sw) = card.read_gid1()
if sw == '9000': if sw == '9000':
print("GID1: %s" % (res,)) print("GID1: %s" % (res,))
else: else:
print("GID1: Can't read, response code = %s" % (sw,)) print("GID1: Can't read, response code = %s" % (sw,))
except Exception as e: except Exception as e:
print("GID1: Can't read file -- %s" % (str(e),)) print("GID1: Can't read file -- %s" % (str(e),))
# EF.GID2 # EF.GID2
try: try:
(res, sw) = card.read_binary('GID2') (res, sw) = card.read_binary('GID2')
if sw == '9000': if sw == '9000':
print("GID2: %s" % (res,)) print("GID2: %s" % (res,))
else: else:
print("GID2: Can't read, response code = %s" % (sw,)) print("GID2: Can't read, response code = %s" % (sw,))
except Exception as e: except Exception as e:
print("GID2: Can't read file -- %s" % (str(e),)) print("GID2: Can't read file -- %s" % (str(e),))
# EF.SMSP # EF.SMSP
(res, sw) = card.read_record('SMSP', 1) (res, sw) = card.read_record('SMSP', 1)
if sw == '9000': if sw == '9000':
print("SMSP: %s" % (res,)) print("SMSP: %s" % (res,))
else: else:
print("SMSP: Can't read, response code = %s" % (sw,)) print("SMSP: Can't read, response code = %s" % (sw,))
# EF.SPN # EF.SPN
try: try:
(res, sw) = card.read_spn() (res, sw) = card.read_spn()
if sw == '9000': if sw == '9000':
print("SPN: %s" % (res[0] or "Not available")) print("SPN: %s" % (res[0] or "Not available"))
print("Show in HPLMN: %s" % (res[1],)) print("Show in HPLMN: %s" % (res[1],))
print("Hide in OPLMN: %s" % (res[2],)) print("Hide in OPLMN: %s" % (res[2],))
else: else:
print("SPN: Can't read, response code = %s" % (sw,)) print("SPN: Can't read, response code = %s" % (sw,))
except Exception as e: except Exception as e:
print("SPN: Can't read file -- %s" % (str(e),)) print("SPN: Can't read file -- %s" % (str(e),))
# EF.PLMNsel # EF.PLMNsel
try: try:
(res, sw) = card.read_binary('PLMNsel') (res, sw) = card.read_binary('PLMNsel')
if sw == '9000': if sw == '9000':
print("PLMNsel: %s" % (res)) print("PLMNsel: %s" % (res))
else: else:
print("PLMNsel: Can't read, response code = %s" % (sw,)) print("PLMNsel: Can't read, response code = %s" % (sw,))
except Exception as e: except Exception as e:
print("PLMNsel: Can't read file -- " + str(e)) print("PLMNsel: Can't read file -- " + str(e))
# EF.PLMNwAcT # EF.PLMNwAcT
try: try:
(res, sw) = card.read_plmn_act() (res, sw) = card.read_plmn_act()
if sw == '9000': if sw == '9000':
print("PLMNwAcT:\n%s" % (res)) print("PLMNwAcT:\n%s" % (res))
else: else:
print("PLMNwAcT: Can't read, response code = %s" % (sw,)) print("PLMNwAcT: Can't read, response code = %s" % (sw,))
except Exception as e: except Exception as e:
print("PLMNwAcT: Can't read file -- " + str(e)) print("PLMNwAcT: Can't read file -- " + str(e))
# EF.OPLMNwAcT # EF.OPLMNwAcT
try: try:
(res, sw) = card.read_oplmn_act() (res, sw) = card.read_oplmn_act()
if sw == '9000': if sw == '9000':
print("OPLMNwAcT:\n%s" % (res)) print("OPLMNwAcT:\n%s" % (res))
else: else:
print("OPLMNwAcT: Can't read, response code = %s" % (sw,)) print("OPLMNwAcT: Can't read, response code = %s" % (sw,))
except Exception as e: except Exception as e:
print("OPLMNwAcT: Can't read file -- " + str(e)) print("OPLMNwAcT: Can't read file -- " + str(e))
# EF.HPLMNAcT # EF.HPLMNAcT
try: try:
(res, sw) = card.read_hplmn_act() (res, sw) = card.read_hplmn_act()
if sw == '9000': if sw == '9000':
print("HPLMNAcT:\n%s" % (res)) print("HPLMNAcT:\n%s" % (res))
else: else:
print("HPLMNAcT: Can't read, response code = %s" % (sw,)) print("HPLMNAcT: Can't read, response code = %s" % (sw,))
except Exception as e: except Exception as e:
print("HPLMNAcT: Can't read file -- " + str(e)) print("HPLMNAcT: Can't read file -- " + str(e))
# EF.ACC # EF.ACC
(res, sw) = card.read_binary('ACC') (res, sw) = card.read_binary('ACC')
if sw == '9000': if sw == '9000':
print("ACC: %s" % (res,)) print("ACC: %s" % (res,))
else: else:
print("ACC: Can't read, response code = %s" % (sw,)) print("ACC: Can't read, response code = %s" % (sw,))
# EF.MSISDN # EF.MSISDN
try: try:
(res, sw) = card.read_msisdn() (res, sw) = card.read_msisdn()
if sw == '9000': if sw == '9000':
# (npi, ton, msisdn) = res # (npi, ton, msisdn) = res
if res is not None: if res is not None:
print("MSISDN (NPI=%d ToN=%d): %s" % res) print("MSISDN (NPI=%d ToN=%d): %s" % res)
else: else:
print("MSISDN: Not available") print("MSISDN: Not available")
else: else:
print("MSISDN: Can't read, response code = %s" % (sw,)) print("MSISDN: Can't read, response code = %s" % (sw,))
except Exception as e: except Exception as e:
print("MSISDN: Can't read file -- " + str(e)) print("MSISDN: Can't read file -- " + str(e))
# EF.AD # EF.AD
(res, sw) = card.read_binary('AD') (res, sw) = card.read_binary('AD')
if sw == '9000': if sw == '9000':
print("Administrative data: %s" % (res,)) print("Administrative data: %s" % (res,))
ad = EF_AD() ad = EF_AD()
decoded_data = ad.decode_hex(res) decoded_data = ad.decode_hex(res)
print("\tMS operation mode: %s" % decoded_data['ms_operation_mode']) print("\tMS operation mode: %s" % decoded_data['ms_operation_mode'])
if decoded_data['ofm']: if decoded_data['ofm']:
print("\tCiphering Indicator: enabled") print("\tCiphering Indicator: enabled")
else: else:
print("\tCiphering Indicator: disabled") print("\tCiphering Indicator: disabled")
else: else:
print("AD: Can't read, response code = %s" % (sw,)) print("AD: Can't read, response code = %s" % (sw,))
# EF.SST # EF.SST
(res, sw) = card.read_binary('SST') (res, sw) = card.read_binary('SST')
if sw == '9000': if sw == '9000':
print("SIM Service Table: %s" % res) print("SIM Service Table: %s" % res)
# Print those which are available # Print those which are available
print("%s" % dec_st(res)) print("%s" % dec_st(res))
else: else:
print("SIM Service Table: Can't read, response code = %s" % (sw,)) 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 # Check whether we have th AID of USIM, if so select it by its AID
# EF.UST - File Id in ADF USIM : 6f38 # EF.UST - File Id in ADF USIM : 6f38
sw = select_app("USIM", card) sw = select_app("USIM", card)
if sw == '9000': if sw == '9000':
# Select USIM profile # Select USIM profile
usim_card = UsimCard(scc) usim_card = UsimCard(scc)
# EF.EHPLMN # EF.EHPLMN
if usim_card.file_exists(EF_USIM_ADF_map['EHPLMN']): if usim_card.file_exists(EF_USIM_ADF_map['EHPLMN']):
(res, sw) = usim_card.read_ehplmn() (res, sw) = usim_card.read_ehplmn()
if sw == '9000': if sw == '9000':
print("EHPLMN:\n%s" % (res)) print("EHPLMN:\n%s" % (res))
else: else:
print("EHPLMN: Can't read, response code = %s" % (sw,)) print("EHPLMN: Can't read, response code = %s" % (sw,))
# EF.UST # EF.UST
try: try:
if usim_card.file_exists(EF_USIM_ADF_map['UST']): if usim_card.file_exists(EF_USIM_ADF_map['UST']):
# res[0] - EF content of UST # res[0] - EF content of UST
# res[1] - Human readable format of services marked available in UST # res[1] - Human readable format of services marked available in UST
(res, sw) = usim_card.read_ust() (res, sw) = usim_card.read_ust()
if sw == '9000': if sw == '9000':
print("USIM Service Table: %s" % res[0]) print("USIM Service Table: %s" % res[0])
print("%s" % res[1]) print("%s" % res[1])
else: else:
print("USIM Service Table: Can't read, response code = %s" % (sw,)) print("USIM Service Table: Can't read, response code = %s" % (sw,))
except Exception as e: except Exception as e:
print("USIM Service Table: Can't read file -- " + str(e)) print("USIM Service Table: Can't read file -- " + str(e))
#EF.ePDGId - Home ePDG Identifier # EF.ePDGId - Home ePDG Identifier
try: try:
if usim_card.file_exists(EF_USIM_ADF_map['ePDGId']): if usim_card.file_exists(EF_USIM_ADF_map['ePDGId']):
(res, sw) = usim_card.read_epdgid() (res, sw) = usim_card.read_epdgid()
if sw == '9000': if sw == '9000':
print("ePDGId:\n%s" % (len(res) and res or '\tNot available\n',)) print("ePDGId:\n%s" %
else: (len(res) and res or '\tNot available\n',))
print("ePDGId: Can't read, response code = %s" % (sw,)) else:
except Exception as e: print("ePDGId: Can't read, response code = %s" % (sw,))
print("ePDGId: Can't read file -- " + str(e)) except Exception as e:
print("ePDGId: Can't read file -- " + str(e))
#EF.ePDGSelection - ePDG Selection Information # EF.ePDGSelection - ePDG Selection Information
try: try:
if usim_card.file_exists(EF_USIM_ADF_map['ePDGSelection']): if usim_card.file_exists(EF_USIM_ADF_map['ePDGSelection']):
(res, sw) = usim_card.read_ePDGSelection() (res, sw) = usim_card.read_ePDGSelection()
if sw == '9000': if sw == '9000':
print("ePDGSelection:\n%s" % (res,)) print("ePDGSelection:\n%s" % (res,))
else: else:
print("ePDGSelection: Can't read, response code = %s" % (sw,)) print("ePDGSelection: Can't read, response code = %s" % (sw,))
except Exception as e: except Exception as e:
print("ePDGSelection: Can't read file -- " + str(e)) print("ePDGSelection: Can't read file -- " + str(e))
# Select ISIM application by its AID # Select ISIM application by its AID
sw = select_app("ISIM", card) sw = select_app("ISIM", card)
if sw == '9000': if sw == '9000':
# Select USIM profile # Select USIM profile
isim_card = IsimCard(scc) isim_card = IsimCard(scc)
#EF.P-CSCF - P-CSCF Address # EF.P-CSCF - P-CSCF Address
try: try:
if isim_card.file_exists(EF_ISIM_ADF_map['PCSCF']): if isim_card.file_exists(EF_ISIM_ADF_map['PCSCF']):
res = isim_card.read_pcscf() res = isim_card.read_pcscf()
print("P-CSCF:\n%s" % (len(res) and res or '\tNot available\n',)) print("P-CSCF:\n%s" %
except Exception as e: (len(res) and res or '\tNot available\n',))
print("P-CSCF: Can't read file -- " + str(e)) 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 # EF.DOMAIN - Home Network Domain Name e.g. ims.mncXXX.mccXXX.3gppnetwork.org
try: try:
if isim_card.file_exists(EF_ISIM_ADF_map['DOMAIN']): if isim_card.file_exists(EF_ISIM_ADF_map['DOMAIN']):
(res, sw) = isim_card.read_domain() (res, sw) = isim_card.read_domain()
if sw == '9000': if sw == '9000':
print("Home Network Domain Name: %s" % (len(res) and res or 'Not available',)) print("Home Network Domain Name: %s" %
else: (len(res) and res or 'Not available',))
print("Home Network Domain Name: Can't read, response code = %s" % (sw,)) else:
except Exception as e: print(
print("Home Network Domain Name: Can't read file -- " + str(e)) "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 # EF.IMPI - IMS private user identity
try: try:
if isim_card.file_exists(EF_ISIM_ADF_map['IMPI']): if isim_card.file_exists(EF_ISIM_ADF_map['IMPI']):
(res, sw) = isim_card.read_impi() (res, sw) = isim_card.read_impi()
if sw == '9000': if sw == '9000':
print("IMS private user identity: %s" % (len(res) and res or 'Not available',)) print("IMS private user identity: %s" %
else: (len(res) and res or 'Not available',))
print("IMS private user identity: Can't read, response code = %s" % (sw,)) else:
except Exception as e: print(
print("IMS private user identity: Can't read file -- " + str(e)) "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 # EF.IMPU - IMS public user identity
try: try:
if isim_card.file_exists(EF_ISIM_ADF_map['IMPU']): if isim_card.file_exists(EF_ISIM_ADF_map['IMPU']):
res = isim_card.read_impu() res = isim_card.read_impu()
print("IMS public user identity:\n%s" % (len(res) and res or '\tNot available\n',)) print("IMS public user identity:\n%s" %
except Exception as e: (len(res) and res or '\tNot available\n',))
print("IMS public user identity: Can't read file -- " + str(e)) except Exception as e:
print("IMS public user identity: Can't read file -- " + str(e))
# EF.UICCIARI - UICC IARI # EF.UICCIARI - UICC IARI
try: try:
if isim_card.file_exists(EF_ISIM_ADF_map['UICCIARI']): if isim_card.file_exists(EF_ISIM_ADF_map['UICCIARI']):
res = isim_card.read_iari() res = isim_card.read_iari()
print("UICC IARI:\n%s" % (len(res) and res or '\tNot available\n',)) print("UICC IARI:\n%s" %
except Exception as e: (len(res) and res or '\tNot available\n',))
print("UICC IARI: Can't read file -- " + str(e)) except Exception as e:
print("UICC IARI: Can't read file -- " + str(e))
# EF.IST # EF.IST
(res, sw) = card.read_binary('6f07') (res, sw) = card.read_binary('6f07')
if sw == '9000': if sw == '9000':
print("ISIM Service Table: %s" % res) print("ISIM Service Table: %s" % res)
# Print those which are available # Print those which are available
print("%s" % dec_st(res, table="isim")) print("%s" % dec_st(res, table="isim"))
else: else:
print("ISIM Service Table: Can't read, response code = %s" % (sw,)) print("ISIM Service Table: Can't read, response code = %s" % (sw,))
# Done for this card and maybe for everything ? # Done for this card and maybe for everything ?
print("Done !\n") 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) # various BER-TLV encoded Data Objects (DOs)
class AidRefDO(BER_TLV_IE, tag=0x4f): class AidRefDO(BER_TLV_IE, tag=0x4f):
# SEID v1.1 Table 6-3 # SEID v1.1 Table 6-3
_construct = HexAdapter(GreedyBytes) _construct = HexAdapter(GreedyBytes)
class AidRefEmptyDO(BER_TLV_IE, tag=0xc0): class AidRefEmptyDO(BER_TLV_IE, tag=0xc0):
# SEID v1.1 Table 6-3 # SEID v1.1 Table 6-3
pass pass
class DevAppIdRefDO(BER_TLV_IE, tag=0xc1): class DevAppIdRefDO(BER_TLV_IE, tag=0xc1):
# SEID v1.1 Table 6-4 # SEID v1.1 Table 6-4
_construct = HexAdapter(GreedyBytes) _construct = HexAdapter(GreedyBytes)
class PkgRefDO(BER_TLV_IE, tag=0xca): class PkgRefDO(BER_TLV_IE, tag=0xca):
# Android UICC Carrier Privileges specific extension, see https://source.android.com/devices/tech/config/uicc # Android UICC Carrier Privileges specific extension, see https://source.android.com/devices/tech/config/uicc
_construct = Struct('package_name_string'/GreedyString("ascii")) _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 # SEID v1.1 Table 6-5
pass pass
class ApduArDO(BER_TLV_IE, tag=0xd0): class ApduArDO(BER_TLV_IE, tag=0xd0):
# SEID v1.1 Table 6-8 # SEID v1.1 Table 6-8
def _from_bytes(self, do:bytes): def _from_bytes(self, do: bytes):
if len(do) == 1: if len(do) == 1:
if do[0] == 0x00: if do[0] == 0x00:
self.decoded = {'generic_access_rule': 'never'} self.decoded = {'generic_access_rule': 'never'}
@ -76,6 +82,7 @@ class ApduArDO(BER_TLV_IE, tag=0xd0):
'mask': b2h(do[offset+4:offset+8])} 'mask': b2h(do[offset+4:offset+8])}
self.decoded = res self.decoded = res
return res return res
def _to_bytes(self): def _to_bytes(self):
if 'generic_access_rule' in self.decoded: if 'generic_access_rule' in self.decoded:
if self.decoded['generic_access_rule'] == 'never': if self.decoded['generic_access_rule'] == 'never':
@ -99,94 +106,118 @@ class ApduArDO(BER_TLV_IE, tag=0xd0):
res += header_b + mask_b res += header_b + mask_b
return res return res
class NfcArDO(BER_TLV_IE, tag=0xd1): class NfcArDO(BER_TLV_IE, tag=0xd1):
# SEID v1.1 Table 6-9 # 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): class PermArDO(BER_TLV_IE, tag=0xdb):
# Android UICC Carrier Privileges specific extension, see https://source.android.com/devices/tech/config/uicc # Android UICC Carrier Privileges specific extension, see https://source.android.com/devices/tech/config/uicc
_construct = Struct('permissions'/HexAdapter(Bytes(8))) _construct = Struct('permissions'/HexAdapter(Bytes(8)))
class ArDO(BER_TLV_IE, tag=0xe3, nested=[ApduArDO, NfcArDO, PermArDO]): class ArDO(BER_TLV_IE, tag=0xe3, nested=[ApduArDO, NfcArDO, PermArDO]):
# SEID v1.1 Table 6-7 # SEID v1.1 Table 6-7
pass pass
class RefArDO(BER_TLV_IE, tag=0xe2, nested=[RefDO, ArDO]): class RefArDO(BER_TLV_IE, tag=0xe2, nested=[RefDO, ArDO]):
# SEID v1.1 Table 6-6 # SEID v1.1 Table 6-6
pass pass
class ResponseAllRefArDO(BER_TLV_IE, tag=0xff40, nested=[RefArDO]): class ResponseAllRefArDO(BER_TLV_IE, tag=0xff40, nested=[RefArDO]):
# SEID v1.1 Table 4-2 # SEID v1.1 Table 4-2
pass pass
class ResponseArDO(BER_TLV_IE, tag=0xff50, nested=[ArDO]): class ResponseArDO(BER_TLV_IE, tag=0xff50, nested=[ArDO]):
# SEID v1.1 Table 4-3 # SEID v1.1 Table 4-3
pass pass
class ResponseRefreshTagDO(BER_TLV_IE, tag=0xdf20): class ResponseRefreshTagDO(BER_TLV_IE, tag=0xdf20):
# SEID v1.1 Table 4-4 # SEID v1.1 Table 4-4
_construct = Struct('refresh_tag'/HexAdapter(Bytes(8))) _construct = Struct('refresh_tag'/HexAdapter(Bytes(8)))
class DeviceInterfaceVersionDO(BER_TLV_IE, tag=0xe6): class DeviceInterfaceVersionDO(BER_TLV_IE, tag=0xe6):
# SEID v1.1 Table 6-12 # SEID v1.1 Table 6-12
_construct = Struct('major'/Int8ub, 'minor'/Int8ub, 'patch'/Int8ub) _construct = Struct('major'/Int8ub, 'minor'/Int8ub, 'patch'/Int8ub)
class DeviceConfigDO(BER_TLV_IE, tag=0xe4, nested=[DeviceInterfaceVersionDO]): class DeviceConfigDO(BER_TLV_IE, tag=0xe4, nested=[DeviceInterfaceVersionDO]):
# SEID v1.1 Table 6-10 # SEID v1.1 Table 6-10
pass pass
class ResponseDeviceConfigDO(BER_TLV_IE, tag=0xff7f, nested=[DeviceConfigDO]): class ResponseDeviceConfigDO(BER_TLV_IE, tag=0xff7f, nested=[DeviceConfigDO]):
# SEID v1.1 Table 5-14 # SEID v1.1 Table 5-14
pass pass
class AramConfigDO(BER_TLV_IE, tag=0xe5, nested=[DeviceInterfaceVersionDO]): class AramConfigDO(BER_TLV_IE, tag=0xe5, nested=[DeviceInterfaceVersionDO]):
# SEID v1.1 Table 6-11 # SEID v1.1 Table 6-11
pass pass
class ResponseAramConfigDO(BER_TLV_IE, tag=0xdf21, nested=[AramConfigDO]): class ResponseAramConfigDO(BER_TLV_IE, tag=0xdf21, nested=[AramConfigDO]):
# SEID v1.1 Table 4-5 # SEID v1.1 Table 4-5
pass pass
class CommandStoreRefArDO(BER_TLV_IE, tag=0xf0, nested=[RefArDO]): class CommandStoreRefArDO(BER_TLV_IE, tag=0xf0, nested=[RefArDO]):
# SEID v1.1 Table 5-2 # SEID v1.1 Table 5-2
pass pass
class CommandDelete(BER_TLV_IE, tag=0xf1, nested=[AidRefDO, AidRefEmptyDO, RefDO, RefArDO]): class CommandDelete(BER_TLV_IE, tag=0xf1, nested=[AidRefDO, AidRefEmptyDO, RefDO, RefArDO]):
# SEID v1.1 Table 5-4 # SEID v1.1 Table 5-4
pass pass
class CommandUpdateRefreshTagDO(BER_TLV_IE, tag=0xf2): class CommandUpdateRefreshTagDO(BER_TLV_IE, tag=0xf2):
# SEID V1.1 Table 5-6 # SEID V1.1 Table 5-6
pass pass
class CommandRegisterClientAidsDO(BER_TLV_IE, tag=0xf7, nested=[AidRefDO, AidRefEmptyDO]): class CommandRegisterClientAidsDO(BER_TLV_IE, tag=0xf7, nested=[AidRefDO, AidRefEmptyDO]):
# SEID v1.1 Table 5-7 # SEID v1.1 Table 5-7
pass pass
class CommandGet(BER_TLV_IE, tag=0xf3, nested=[AidRefDO, AidRefEmptyDO]): class CommandGet(BER_TLV_IE, tag=0xf3, nested=[AidRefDO, AidRefEmptyDO]):
# SEID v1.1 Table 5-8 # SEID v1.1 Table 5-8
pass pass
class CommandGetAll(BER_TLV_IE, tag=0xf4): class CommandGetAll(BER_TLV_IE, tag=0xf4):
# SEID v1.1 Table 5-9 # SEID v1.1 Table 5-9
pass pass
class CommandGetClientAidsDO(BER_TLV_IE, tag=0xf6): class CommandGetClientAidsDO(BER_TLV_IE, tag=0xf6):
# SEID v1.1 Table 5-10 # SEID v1.1 Table 5-10
pass pass
class CommandGetNext(BER_TLV_IE, tag=0xf5): class CommandGetNext(BER_TLV_IE, tag=0xf5):
# SEID v1.1 Table 5-11 # SEID v1.1 Table 5-11
pass pass
class CommandGetDeviceConfigDO(BER_TLV_IE, tag=0xf8): class CommandGetDeviceConfigDO(BER_TLV_IE, tag=0xf8):
# SEID v1.1 Table 5-12 # SEID v1.1 Table 5-12
pass pass
class ResponseAracAidDO(BER_TLV_IE, tag=0xff70, nested=[AidRefDO, AidRefEmptyDO]): class ResponseAracAidDO(BER_TLV_IE, tag=0xff70, nested=[AidRefDO, AidRefEmptyDO]):
# SEID v1.1 Table 5-13 # SEID v1.1 Table 5-13
pass pass
class BlockDO(BER_TLV_IE, tag=0xe7): class BlockDO(BER_TLV_IE, tag=0xe7):
# SEID v1.1 Table 6-13 # SEID v1.1 Table 6-13
_construct = Struct('offset'/Int16ub, 'length'/Int8ub) _construct = Struct('offset'/Int16ub, 'length'/Int8ub)
@ -197,11 +228,15 @@ class GetCommandDoCollection(TLV_IE_Collection, nested=[RefDO, DeviceConfigDO]):
pass pass
# SEID v1.1 Table 4-2 # SEID v1.1 Table 4-2
class GetResponseDoCollection(TLV_IE_Collection, nested=[ResponseAllRefArDO, ResponseArDO, class GetResponseDoCollection(TLV_IE_Collection, nested=[ResponseAllRefArDO, ResponseArDO,
ResponseRefreshTagDO, ResponseAramConfigDO]): ResponseRefreshTagDO, ResponseAramConfigDO]):
pass pass
# SEID v1.1 Table 5-1 # SEID v1.1 Table 5-1
class StoreCommandDoCollection(TLV_IE_Collection, class StoreCommandDoCollection(TLV_IE_Collection,
nested=[BlockDO, CommandStoreRefArDO, CommandDelete, nested=[BlockDO, CommandStoreRefArDO, CommandDelete,
CommandUpdateRefreshTagDO, CommandRegisterClientAidsDO, CommandUpdateRefreshTagDO, CommandRegisterClientAidsDO,
@ -215,6 +250,7 @@ class StoreResponseDoCollection(TLV_IE_Collection,
nested=[ResponseAllRefArDO, ResponseAracAidDO, ResponseDeviceConfigDO]): nested=[ResponseAllRefArDO, ResponseAracAidDO, ResponseDeviceConfigDO]):
pass pass
class ADF_ARAM(CardADF): class ADF_ARAM(CardADF):
def __init__(self, aid='a00000015141434c00', name='ADF.ARA-M', fid=None, sfid=None, def __init__(self, aid='a00000015141434c00', name='ADF.ARA-M', fid=None, sfid=None,
desc='ARA-M Application'): desc='ARA-M Application'):
@ -224,7 +260,7 @@ class ADF_ARAM(CardADF):
self.add_files(files) self.add_files(files)
@staticmethod @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 """Transceive an APDU with the card, transparently encoding the command data from TLV
and decoding the response data tlv.""" and decoding the response data tlv."""
if cmd_do: if cmd_do:
@ -259,7 +295,8 @@ class ADF_ARAM(CardADF):
@staticmethod @staticmethod
def get_config(tp, v_major=0, v_minor=0, v_patch=1): def get_config(tp, v_major=0, v_minor=0, v_patch=1):
cmd_do = DeviceConfigDO() 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) return ADF_ARAM.xceive_apdu_tlv(tp, '80cadf21', cmd_do, ResponseAramConfigDO)
@with_default_category('Application-Specific Commands') @with_default_category('Application-Specific Commands')
@ -281,20 +318,30 @@ class ADF_ARAM(CardADF):
store_ref_ar_do_parse = argparse.ArgumentParser() store_ref_ar_do_parse = argparse.ArgumentParser()
# REF-DO # 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 = 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_grp.add_argument('--aid-empty', action='store_true', help='No specific SE application, applies to all applications') '--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)')
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-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 # AR-DO
apdu_grp = store_ref_ar_do_parse.add_mutually_exclusive_group() 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_grp.add_argument('--apdu-always', action='store_true', help='APDU access is allowed') '--apdu-never', action='store_true', help='APDU access is not 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-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 = 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-always', action='store_true',
nfc_grp.add_argument('--nfc-never', action='store_true', help='NFC event access is not allowed') help='NFC event access is allowed')
store_ref_ar_do_parse.add_argument('--android-permissions', help='Android UICC Carrier Privilege Permissions (8 hex bytes)') 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) @cmd2.with_argparser(store_ref_ar_do_parse)
def do_aram_store_ref_ar_do(self, opts): 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'}}] ar_do_content += [{'NfcArDO': {'nfc_event_access_rule': 'never'}}]
if opts.android_permissions: if opts.android_permissions:
ar_do_content += [{'PermArDO': {'permissions': 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 = CommandStoreRefArDO()
csrado.from_dict(d) csrado.from_dict(d)
res_do = ADF_ARAM.store_data(self._cmd.card._scc._tp, csrado) res_do = ADF_ARAM.store_data(self._cmd.card._scc._tp, csrado)
@ -359,6 +406,7 @@ sw_aram = {
} }
} }
class CardApplicationARAM(CardApplication): class CardApplicationARAM(CardApplication):
def __init__(self): 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 sys
import yaml import yaml
class CardHandlerBase: 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): def __init__(self, sl: LinkBase):
self.sl = sl self.sl = sl
def get(self, first:bool = False): def get(self, first: bool = False):
"""Method called when pySim needs a new card to be inserted. """Method called when pySim needs a new card to be inserted.
Args: Args:
first : set to true when the get method is called the first : set to true when the get method is called the
first time. This is required to prevent blocking first time. This is required to prevent blocking
when a card is already inserted into the reader. when a card is already inserted into the reader.
The reader API would not recognize that card as The reader API would not recognize that card as
"new card" until it would be removed and re-inserted "new card" until it would be removed and re-inserted
again. again.
""" """
print("Ready for Programming: ", end='') print("Ready for Programming: ", end='')
self._get(first) self._get(first)
def error(self): def error(self):
"""Method called when pySim failed to program a card. Move card to 'bad' batch.""" """Method called when pySim failed to program a card. Move card to 'bad' batch."""
print("Programming failed: ", end='') print("Programming failed: ", end='')
self._error() self._error()
def done(self): def done(self):
"""Method called when pySim failed to program a card. Move card to 'good' batch.""" """Method called when pySim failed to program a card. Move card to 'good' batch."""
print("Programming successful: ", end='') print("Programming successful: ", end='')
self._done() self._done()
def _get(self, first:bool = False): def _get(self, first: bool = False):
pass pass
def _error(self): def _error(self):
pass pass
def _done(self): def _done(self):
pass pass
class CardHandler(CardHandlerBase): 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): def _get(self, first: bool = False):
print("Insert card now (or CTRL-C to cancel)") print("Insert card now (or CTRL-C to cancel)")
self.sl.wait_for_card(newcardonly=not first) self.sl.wait_for_card(newcardonly=not first)
def _error(self): def _error(self):
print("Remove card from reader") print("Remove card from reader")
print("") print("")
def _done(self): def _done(self):
print("Remove card from reader") print("Remove card from reader")
print("") print("")
class CardHandlerAuto(CardHandlerBase): 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): def __init__(self, sl: LinkBase, config_file: str):
super().__init__(sl) super().__init__(sl)
print("Card handler Config-file: " + str(config_file)) print("Card handler Config-file: " + str(config_file))
with open(config_file) as cfg: with open(config_file) as cfg:
self.cmds = yaml.load(cfg, Loader=yaml.FullLoader) self.cmds = yaml.load(cfg, Loader=yaml.FullLoader)
self.verbose = (self.cmds.get('verbose') == True) self.verbose = (self.cmds.get('verbose') == True)
def __print_outout(self, out): def __print_outout(self, out):
print("") print("")
print("Card handler output:") print("Card handler output:")
print("---------------------8<---------------------") print("---------------------8<---------------------")
stdout = out[0].strip() stdout = out[0].strip()
if len(stdout) > 0: if len(stdout) > 0:
print("stdout:") print("stdout:")
print(stdout) print(stdout)
stderr = out[1].strip() stderr = out[1].strip()
if len(stderr) > 0: if len(stderr) > 0:
print("stderr:") print("stderr:")
print(stderr) print(stderr)
print("---------------------8<---------------------") print("---------------------8<---------------------")
print("") print("")
def __exec_cmd(self, command): def __exec_cmd(self, command):
print("Card handler Commandline: " + str(command)) print("Card handler Commandline: " + str(command))
proc = subprocess.Popen([command], stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True) proc = subprocess.Popen(
out = proc.communicate() [command], stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True)
rc = proc.returncode out = proc.communicate()
rc = proc.returncode
if rc != 0 or self.verbose: if rc != 0 or self.verbose:
self.__print_outout(out) self.__print_outout(out)
if rc != 0: if rc != 0:
print("") print("")
print("Error: Card handler failure! (rc=" + str(rc) + ")") print("Error: Card handler failure! (rc=" + str(rc) + ")")
sys.exit(rc) sys.exit(rc)
def _get(self, first:bool = False): def _get(self, first: bool = False):
print("Transporting card into the reader-bay...") print("Transporting card into the reader-bay...")
self.__exec_cmd(self.cmds['get']) self.__exec_cmd(self.cmds['get'])
if self.sl: if self.sl:
self.sl.connect() self.sl.connect()
def _error(self): def _error(self):
print("Transporting card to the error-bin...") print("Transporting card to the error-bin...")
self.__exec_cmd(self.cmds['error']) self.__exec_cmd(self.cmds['error'])
print("") print("")
def _done(self): def _done(self):
print("Transporting card into the collector bin...") print("Transporting card into the collector bin...")
self.__exec_cmd(self.cmds['done']) self.__exec_cmd(self.cmds['done'])
print("") print("")

View File

@ -33,136 +33,142 @@ from typing import List, Dict, Optional
import abc import abc
import csv import csv
card_key_providers = [] # type: List['CardKeyProvider'] card_key_providers = [] # type: List['CardKeyProvider']
class CardKeyProvider(abc.ABC): 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 # check input parameters, but do nothing concrete yet
def _verify_get_data(self, fields:List[str]=[], key:str='ICCID', value:str="") -> Dict[str,str]: def _verify_get_data(self, fields: List[str] = [], key: str = 'ICCID', value: str = "") -> Dict[str, str]:
"""Verify multiple fields for identified card. """Verify multiple fields for identified card.
Args: Args:
fields : list of valid field names such as 'ADM1', 'PIN1', ... which are to be obtained 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' key : look-up key to identify card data, such as 'ICCID'
value : value for look-up key to identify card data value : value for look-up key to identify card data
Returns: Returns:
dictionary of {field, value} strings for each requested field from 'fields' dictionary of {field, value} strings for each requested field from 'fields'
""" """
for f in fields: for f in fields:
if (f not in self.VALID_FIELD_NAMES): 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" % raise ValueError("Requested field name '%s' is not a valid field name, valid field names are: %s" %
(f, str(self.VALID_FIELD_NAMES))) (f, str(self.VALID_FIELD_NAMES)))
if (key not in 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" % raise ValueError("Key field name '%s' is not a valid field name, valid field names are: %s" %
(key, str(self.VALID_FIELD_NAMES))) (key, str(self.VALID_FIELD_NAMES)))
return {} return {}
def get_field(self, field:str, key:str='ICCID', value:str="") -> Optional[str]: 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""" """get a single field from CSV file using a specified key/value pair"""
fields = [field] fields = [field]
result = self.get(fields, key, value) result = self.get(fields, key, value)
return result.get(field) return result.get(field)
@abc.abstractmethod @abc.abstractmethod
def get(self, fields:List[str], key:str, value:str) -> Dict[str,str]: def get(self, fields: List[str], key: str, value: str) -> Dict[str, str]:
"""Get multiple card-individual fields for identified card. """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): class CardKeyProviderCsv(CardKeyProvider):
"""Card key provider implementation that allows to query against a specified CSV file""" """Card key provider implementation that allows to query against a specified CSV file"""
csv_file = None csv_file = None
filename = None filename = None
def __init__(self, filename:str): def __init__(self, filename: str):
""" """
Args: Args:
filename : file name (path) of CSV file containing card-individual key/data filename : file name (path) of CSV file containing card-individual key/data
""" """
self.csv_file = open(filename, 'r') self.csv_file = open(filename, 'r')
if not self.csv_file: if not self.csv_file:
raise RuntimeError("Could not open CSV file '%s'" % filename) raise RuntimeError("Could not open CSV file '%s'" % filename)
self.filename = filename self.filename = filename
def get(self, fields:List[str], key:str, value:str) -> Dict[str,str]: def get(self, fields: List[str], key: str, value: str) -> Dict[str, str]:
super()._verify_get_data(fields, key, value) super()._verify_get_data(fields, key, value)
self.csv_file.seek(0) self.csv_file.seek(0)
cr = csv.DictReader(self.csv_file) cr = csv.DictReader(self.csv_file)
if not cr: if not cr:
raise RuntimeError("Could not open DictReader for CSV-File '%s'" % self.filename) raise RuntimeError(
cr.fieldnames = [ field.upper() for field in cr.fieldnames ] "Could not open DictReader for CSV-File '%s'" % self.filename)
cr.fieldnames = [field.upper() for field in cr.fieldnames]
rc = {} rc = {}
for row in cr: for row in cr:
if row[key] == value: if row[key] == value:
for f in fields: for f in fields:
if f in row: if f in row:
rc.update({f : row[f]}) rc.update({f: row[f]})
else: else:
raise RuntimeError("CSV-File '%s' lacks column '%s'" % raise RuntimeError("CSV-File '%s' lacks column '%s'" %
(self.filename, f)) (self.filename, f))
return rc return rc
def card_key_provider_register(provider:CardKeyProvider, provider_list=card_key_providers): def card_key_provider_register(provider: CardKeyProvider, provider_list=card_key_providers):
"""Register a new card key provider. """Register a new card key provider.
Args: Args:
provider : the to-be-registered provider provider : the to-be-registered provider
provider_list : override the list of providers from the global default provider_list : override the list of providers from the global default
""" """
if not isinstance(provider, CardKeyProvider): if not isinstance(provider, CardKeyProvider):
raise ValueError("provider is not a card data provier") raise ValueError("provider is not a card data provier")
provider_list.append(provider) provider_list.append(provider)
def card_key_provider_get(fields, key:str, value:str, provider_list=card_key_providers) -> Dict[str,str]: 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. """Query all registered card data providers for card-individual [key] data.
Args: Args:
fields : list of valid field names such as 'ADM1', 'PIN1', ... which are to be obtained 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' key : look-up key to identify card data, such as 'ICCID'
value : value for look-up key to identify card data value : value for look-up key to identify card data
provider_list : override the list of providers from the global default provider_list : override the list of providers from the global default
Returns: Returns:
dictionary of {field, value} strings for each requested field from 'fields' dictionary of {field, value} strings for each requested field from 'fields'
""" """
for p in provider_list: for p in provider_list:
if not isinstance(p, CardKeyProvider): if not isinstance(p, CardKeyProvider):
raise ValueError("provider list contains element which is not a card data provier") raise ValueError(
result = p.get(fields, key, value) "provider list contains element which is not a card data provier")
if result: result = p.get(fields, key, value)
return result if result:
return {} return result
return {}
def card_key_provider_get_field(field:str, key:str, value:str, provider_list=card_key_providers) -> Optional[str]: 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. """Query all registered card data providers for a single field.
Args: Args:
field : name valid field such as 'ADM1', 'PIN1', ... which is to be obtained field : name valid field such as 'ADM1', 'PIN1', ... which is to be obtained
key : look-up key to identify card data, such as 'ICCID' key : look-up key to identify card data, such as 'ICCID'
value : value for look-up key to identify card data value : value for look-up key to identify card data
provider_list : override the list of providers from the global default provider_list : override the list of providers from the global default
Returns: Returns:
dictionary of {field, value} strings for the requested field dictionary of {field, value} strings for the requested field
""" """
for p in provider_list: for p in provider_list:
if not isinstance(p, CardKeyProvider): if not isinstance(p, CardKeyProvider):
raise ValueError("provider list contains element which is not a card data provier") raise ValueError(
result = p.get_field(field, key, value) "provider list contains element which is not a card data provier")
if result: result = p.get_field(field, key, value)
return result if result:
return None 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 # Tag values as per TS 101 220 Table 7.23
# TS 102 223 Section 8.1 # TS 102 223 Section 8.1
class Address(COMPR_TLV_IE, tag=0x06): class Address(COMPR_TLV_IE, tag=0x06):
_construct = Struct('ton_npi'/Int8ub, _construct = Struct('ton_npi'/Int8ub,
'call_number'/BcdAdapter(Bytes(this._.total_len-1))) 'call_number'/BcdAdapter(Bytes(this._.total_len-1)))
# TS 102 223 Section 8.2 # TS 102 223 Section 8.2
class AlphaIdentifier(COMPR_TLV_IE, tag=0x05): class AlphaIdentifier(COMPR_TLV_IE, tag=0x05):
# FIXME: like EF.ADN # FIXME: like EF.ADN
pass pass
# TS 102 223 Section 8.3 # TS 102 223 Section 8.3
class Subaddress(COMPR_TLV_IE, tag=0x08): class Subaddress(COMPR_TLV_IE, tag=0x08):
pass pass
# TS 102 223 Section 8.4 # TS 102 223 Section 8.4
class CapabilityConfigParams(COMPR_TLV_IE, tag=0x07): class CapabilityConfigParams(COMPR_TLV_IE, tag=0x07):
pass pass
# TS 31.111 Section 8.5 # TS 31.111 Section 8.5
class CBSPage(COMPR_TLV_IE, tag=0x0C): class CBSPage(COMPR_TLV_IE, tag=0x0C):
pass pass
# TS 102 223 Section 8.6 # TS 102 223 Section 8.6
class CommandDetails(COMPR_TLV_IE, tag=0x01): class CommandDetails(COMPR_TLV_IE, tag=0x01):
_construct = Struct('command_number'/Int8ub, _construct = Struct('command_number'/Int8ub,
'type_of_command'/Int8ub, 'type_of_command'/Int8ub,
'command_qualifier'/Int8ub) 'command_qualifier'/Int8ub)
# TS 102 223 Section 8.7 # TS 102 223 Section 8.7
class DeviceIdentities(COMPR_TLV_IE, tag=0x82): class DeviceIdentities(COMPR_TLV_IE, tag=0x82):
DEV_IDS = bidict({ DEV_IDS = bidict({
0x01: 'keypad', 0x01: 'keypad',
@ -90,8 +104,9 @@ class DeviceIdentities(COMPR_TLV_IE, tag=0x82):
0x81: 'uicc', 0x81: 'uicc',
0x82: 'terminal', 0x82: 'terminal',
0x83: 'network', 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]]} return {'source_dev_id': self.DEV_IDS[do[0]], 'dest_dev_id': self.DEV_IDS[do[1]]}
def _to_bytes(self): def _to_bytes(self):
@ -100,200 +115,219 @@ class DeviceIdentities(COMPR_TLV_IE, tag=0x82):
return bytes([src, dst]) return bytes([src, dst])
# TS 102 223 Section 8.8 # TS 102 223 Section 8.8
class Duration(COMPR_TLV_IE, tag=0x04): class Duration(COMPR_TLV_IE, tag=0x04):
_construct = Struct('time_unit'/Int8ub, _construct = Struct('time_unit'/Int8ub,
'time_interval'/Int8ub) 'time_interval'/Int8ub)
# TS 102 223 Section 8.9 # TS 102 223 Section 8.9
class Item(COMPR_TLV_IE, tag=0x0f): class Item(COMPR_TLV_IE, tag=0x0f):
_construct = Struct('identifier'/Int8ub, _construct = Struct('identifier'/Int8ub,
'text_string'/GsmStringAdapter(GreedyBytes)) 'text_string'/GsmStringAdapter(GreedyBytes))
# TS 102 223 Section 8.10 # TS 102 223 Section 8.10
class ItemIdentifier(COMPR_TLV_IE, tag=0x10): class ItemIdentifier(COMPR_TLV_IE, tag=0x10):
_construct = Struct('identifier'/Int8ub) _construct = Struct('identifier'/Int8ub)
# TS 102 223 Section 8.11 # TS 102 223 Section 8.11
class ResponseLength(COMPR_TLV_IE, tag=0x11): class ResponseLength(COMPR_TLV_IE, tag=0x11):
_construct = Struct('minimum_length'/Int8ub, _construct = Struct('minimum_length'/Int8ub,
'maximum_length'/Int8ub) 'maximum_length'/Int8ub)
# TS 102 223 Section 8.12 # TS 102 223 Section 8.12
class Result(COMPR_TLV_IE, tag=0x03): class Result(COMPR_TLV_IE, tag=0x03):
_construct = Struct('general_result'/Int8ub, _construct = Struct('general_result'/Int8ub,
'additional_information'/HexAdapter(GreedyBytes)) 'additional_information'/HexAdapter(GreedyBytes))
# TS 102 223 Section 8.13 + TS 31.111 Section 8.13 # TS 102 223 Section 8.13 + TS 31.111 Section 8.13
class SMS_TPDU(COMPR_TLV_IE, tag=0x8B): class SMS_TPDU(COMPR_TLV_IE, tag=0x8B):
_construct = Struct('tpdu'/HexAdapter(GreedyBytes)) _construct = Struct('tpdu'/HexAdapter(GreedyBytes))
# TS 102 223 Section 8.15 # TS 102 223 Section 8.15
class TextString(COMPR_TLV_IE, tag=0x0d): class TextString(COMPR_TLV_IE, tag=0x0d):
_construct = Struct('dcs'/Int8ub, _construct = Struct('dcs'/Int8ub,
'text_string'/HexAdapter(GreedyBytes)) 'text_string'/HexAdapter(GreedyBytes))
# TS 102 223 Section 8.16 # TS 102 223 Section 8.16
class Tone(COMPR_TLV_IE, tag=0x0e): class Tone(COMPR_TLV_IE, tag=0x0e):
_construct = Struct('tone'/Int8ub) _construct = Struct('tone'/Int8ub)
# TS 31 111 Section 8.17 # TS 31 111 Section 8.17
class USSDString(COMPR_TLV_IE, tag=0x0a): class USSDString(COMPR_TLV_IE, tag=0x0a):
_construct = Struct('dcs'/Int8ub, _construct = Struct('dcs'/Int8ub,
'ussd_string'/HexAdapter(GreedyBytes)) 'ussd_string'/HexAdapter(GreedyBytes))
# TS 101 220 Table 7.17 # TS 101 220 Table 7.17
class ProactiveCommand(BER_TLV_IE, tag=0xD0): class ProactiveCommand(BER_TLV_IE, tag=0xD0):
pass pass
# TS 101 220 Table 7.17 + 31.111 7.1.1.2 # TS 101 220 Table 7.17 + 31.111 7.1.1.2
class SMSPPDownload(BER_TLV_IE, tag=0xD1, class SMSPPDownload(BER_TLV_IE, tag=0xD1,
nested=[DeviceIdentities, Address, SMS_TPDU]): nested=[DeviceIdentities, Address, SMS_TPDU]):
pass pass
# TS 101 220 Table 7.17 + 31.111 7.1.1.3 # TS 101 220 Table 7.17 + 31.111 7.1.1.3
class SMSCBDownload(BER_TLV_IE, tag=0xD2, class SMSCBDownload(BER_TLV_IE, tag=0xD2,
nested=[DeviceIdentities, CBSPage]): nested=[DeviceIdentities, CBSPage]):
pass pass
class USSDDownload(BER_TLV_IE, tag=0xD9, class USSDDownload(BER_TLV_IE, tag=0xD9,
nested=[DeviceIdentities, USSDString]): nested=[DeviceIdentities, USSDString]):
pass pass
# reasonable default for playing with OTA # reasonable default for playing with OTA
# 010203040506070809101112131415161718192021222324252627282930313233 # 010203040506070809101112131415161718192021222324252627282930313233
#'7fe1e10e000000000000001f43000000ff00000000000000000000000000000000' # '7fe1e10e000000000000001f43000000ff00000000000000000000000000000000'
# TS 102 223 Section 5.2 # TS 102 223 Section 5.2
term_prof_bits = { term_prof_bits = {
# first byte # first byte
1: 'Profile download', 1: 'Profile download',
2: 'SMS-PP data download', 2: 'SMS-PP data download',
3: 'Cell Broadcast data download', 3: 'Cell Broadcast data download',
4: 'Menu selection', 4: 'Menu selection',
5: 'SMS-PP data download', 5: 'SMS-PP data download',
6: 'Timer expiration', 6: 'Timer expiration',
7: 'USSD string DO support in CC by USIM', 7: 'USSD string DO support in CC by USIM',
8: 'Call Control by NAA', 8: 'Call Control by NAA',
# first byte # first byte
9: 'Command result', 9: 'Command result',
10: 'Call Control by NAA', 10: 'Call Control by NAA',
11: 'Call Control by NAA', 11: 'Call Control by NAA',
12: 'MO short message control support', 12: 'MO short message control support',
13: 'Call Control by NAA', 13: 'Call Control by NAA',
14: 'UCS2 Entry supported', 14: 'UCS2 Entry supported',
15: 'UCS2 Display supported', 15: 'UCS2 Display supported',
16: 'Display Text', 16: 'Display Text',
# third byte # third byte
17: 'Proactive UICC: DISPLAY TEXT', 17: 'Proactive UICC: DISPLAY TEXT',
18: 'Proactive UICC: GET INKEY', 18: 'Proactive UICC: GET INKEY',
19: 'Proactive UICC: GET INPUT', 19: 'Proactive UICC: GET INPUT',
20: 'Proactive UICC: MORE TIME', 20: 'Proactive UICC: MORE TIME',
21: 'Proactive UICC: PLAY TONE', 21: 'Proactive UICC: PLAY TONE',
22: 'Proactive UICC: POLL INTERVAL', 22: 'Proactive UICC: POLL INTERVAL',
23: 'Proactive UICC: POLLING OFF', 23: 'Proactive UICC: POLLING OFF',
24: 'Proactive UICC: REFRESH', 24: 'Proactive UICC: REFRESH',
# fourth byte # fourth byte
25: 'Proactive UICC: SELECT ITEM', 25: 'Proactive UICC: SELECT ITEM',
26: 'Proactive UICC: SEND SHORT MESSAGE with 3GPP-SMS-TPDU', 26: 'Proactive UICC: SEND SHORT MESSAGE with 3GPP-SMS-TPDU',
27: 'Proactive UICC: SEND SS', 27: 'Proactive UICC: SEND SS',
28: 'Proactive UICC: SEND USSD', 28: 'Proactive UICC: SEND USSD',
29: 'Proactive UICC: SET UP CALL', 29: 'Proactive UICC: SET UP CALL',
30: 'Proactive UICC: SET UP MENU', 30: 'Proactive UICC: SET UP MENU',
31: 'Proactive UICC: PROVIDE LOCAL INFORMATION (MCC, MNC, LAC, Cell ID & IMEI)', 31: 'Proactive UICC: PROVIDE LOCAL INFORMATION (MCC, MNC, LAC, Cell ID & IMEI)',
32: 'Proactive UICC: PROVIDE LOCAL INFORMATION (NMR)', 32: 'Proactive UICC: PROVIDE LOCAL INFORMATION (NMR)',
# fifth byte # fifth byte
33: 'Proactive UICC: SET UP EVENT LIST', 33: 'Proactive UICC: SET UP EVENT LIST',
34: 'Event: MT call', 34: 'Event: MT call',
35: 'Event: Call connected', 35: 'Event: Call connected',
36: 'Event: Call disconnected', 36: 'Event: Call disconnected',
37: 'Event: Location status', 37: 'Event: Location status',
38: 'Event: User activity', 38: 'Event: User activity',
39: 'Event: Idle screen available', 39: 'Event: Idle screen available',
40: 'Event: Card reader status', 40: 'Event: Card reader status',
# sixth byte # sixth byte
41: 'Event: Language selection', 41: 'Event: Language selection',
42: 'Event: Browser Termination', 42: 'Event: Browser Termination',
43: 'Event: Data aailable', 43: 'Event: Data aailable',
44: 'Event: Channel status', 44: 'Event: Channel status',
45: 'Event: Access Technology Change', 45: 'Event: Access Technology Change',
46: 'Event: Display parameters changed', 46: 'Event: Display parameters changed',
47: 'Event: Local Connection', 47: 'Event: Local Connection',
48: 'Event: Network Search Mode Change', 48: 'Event: Network Search Mode Change',
# seventh byte # seventh byte
49: 'Proactive UICC: POWER ON CARD', 49: 'Proactive UICC: POWER ON CARD',
50: 'Proactive UICC: POWER OFF CARD', 50: 'Proactive UICC: POWER OFF CARD',
51: 'Proactive UICC: PERFORM CARD RESET', 51: 'Proactive UICC: PERFORM CARD RESET',
52: 'Proactive UICC: GET READER STATUS (Card reader status)', 52: 'Proactive UICC: GET READER STATUS (Card reader status)',
53: 'Proactive UICC: GET READER STATUS (Card reader identifier)', 53: 'Proactive UICC: GET READER STATUS (Card reader identifier)',
# RFU: 3 bit (54,55,56) # RFU: 3 bit (54,55,56)
# eighth byte # eighth byte
57: 'Proactive UICC: TIMER MANAGEMENT (start, stop)', 57: 'Proactive UICC: TIMER MANAGEMENT (start, stop)',
58: 'Proactive UICC: TIMER MANAGEMENT (get current value)', 58: 'Proactive UICC: TIMER MANAGEMENT (get current value)',
59: 'Proactive UICC: PROVIDE LOCAL INFORMATION (date, time and time zone)', 59: 'Proactive UICC: PROVIDE LOCAL INFORMATION (date, time and time zone)',
60: 'GET INKEY', 60: 'GET INKEY',
61: 'SET UP IDLE MODE TEXT', 61: 'SET UP IDLE MODE TEXT',
62: 'RUN AT COMMAND', 62: 'RUN AT COMMAND',
63: 'SETUP CALL', 63: 'SETUP CALL',
64: 'Call Control by NAA', 64: 'Call Control by NAA',
# ninth byte # ninth byte
65: 'DISPLAY TEXT', 65: 'DISPLAY TEXT',
66: 'SEND DTMF command', 66: 'SEND DTMF command',
67: 'Proactive UICC: PROVIDE LOCAL INFORMATION (NMR)', 67: 'Proactive UICC: PROVIDE LOCAL INFORMATION (NMR)',
68: 'Proactive UICC: PROVIDE LOCAL INFORMATION (language)', 68: 'Proactive UICC: PROVIDE LOCAL INFORMATION (language)',
69: 'Proactive UICC: PROVIDE LOCAL INFORMATION (Timing Advance)', 69: 'Proactive UICC: PROVIDE LOCAL INFORMATION (Timing Advance)',
70: 'Proactive UICC: LANGUAGE NOTIFICATION', 70: 'Proactive UICC: LANGUAGE NOTIFICATION',
71: 'Proactive UICC: LAUNCH BROWSER', 71: 'Proactive UICC: LAUNCH BROWSER',
72: 'Proactive UICC: PROVIDE LOCAL INFORMATION (Access Technology)', 72: 'Proactive UICC: PROVIDE LOCAL INFORMATION (Access Technology)',
# tenth byte # tenth byte
73: 'Soft keys support for SELECT ITEM', 73: 'Soft keys support for SELECT ITEM',
74: 'Soft keys support for SET UP MENU ITEM', 74: 'Soft keys support for SET UP MENU ITEM',
# RFU: 6 bit (75-80) # 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 # twelfth byte
89: 'Proactive UICC: OPEN CHANNEL', 89: 'Proactive UICC: OPEN CHANNEL',
90: 'Proactive UICC: CLOSE CHANNEL', 90: 'Proactive UICC: CLOSE CHANNEL',
91: 'Proactive UICC: RECEIVE DATA', 91: 'Proactive UICC: RECEIVE DATA',
92: 'Proactive UICC: SEND DATA', 92: 'Proactive UICC: SEND DATA',
93: 'Proactive UICC: GET CHANNEL STATUS', 93: 'Proactive UICC: GET CHANNEL STATUS',
94: 'Proactive UICC: SERVICE SEARCH', 94: 'Proactive UICC: SERVICE SEARCH',
95: 'Proactive UICC: GET SERVICE INFORMATION', 95: 'Proactive UICC: GET SERVICE INFORMATION',
96: 'Proactive UICC: DECLARE SERVICE', 96: 'Proactive UICC: DECLARE SERVICE',
# thirteenth byte # thirteenth byte
97: 'BIP supported Bearer: CSD', 97: 'BIP supported Bearer: CSD',
98: 'BIP supported Bearer: GPRS', 98: 'BIP supported Bearer: GPRS',
99: 'BIP supported Bearer: Bluetooth', 99: 'BIP supported Bearer: Bluetooth',
100: 'BIP supported Bearer: IrDA', 100: 'BIP supported Bearer: IrDA',
101: 'BIP supported Bearer: RS232', 101: 'BIP supported Bearer: RS232',
# 3 bits: number of channels supported (102..104) # 3 bits: number of channels supported (102..104)
# fourtheenth byte (screen height) # fourtheenth byte (screen height)
# fifteenth byte (screen width) # fifteenth byte (screen width)
# sixeenth byte (screen effects) # sixeenth byte (screen effects)
# seventeenth byte (BIP supported bearers) # seventeenth byte (BIP supported bearers)
129: 'BIP: TCP, UICC in client mode, remote connection', 129: 'BIP: TCP, UICC in client mode, remote connection',
130: 'BIP: UDP, UICC in client mode, remote connection', 130: 'BIP: UDP, UICC in client mode, remote connection',
131: 'BIP: TCP, UICC in server mode', 131: 'BIP: TCP, UICC in server mode',
132: 'BIP: TCP, UICC in client mode, local connection', 132: 'BIP: TCP, UICC in client mode, local connection',
133: 'BIP: UDP, UICC in client mode, local connection', 133: 'BIP: UDP, UICC in client mode, local connection',
134: 'BIP: direct communication channel', 134: 'BIP: direct communication channel',
# 2 bits reserved: 135, 136 # 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 import typing
from construct import * from construct import *
from pySim.utils import b2h, h2b, swap_nibbles from pySim.utils import b2h, h2b, swap_nibbles
@ -23,18 +25,24 @@ import gsm0338
class HexAdapter(Adapter): class HexAdapter(Adapter):
"""convert a bytes() type to a string of hex nibbles.""" """convert a bytes() type to a string of hex nibbles."""
def _decode(self, obj, context, path): def _decode(self, obj, context, path):
return b2h(obj) return b2h(obj)
def _encode(self, obj, context, path): def _encode(self, obj, context, path):
return h2b(obj) return h2b(obj)
class BcdAdapter(Adapter): class BcdAdapter(Adapter):
"""convert a bytes() type to a string of BCD nibbles.""" """convert a bytes() type to a string of BCD nibbles."""
def _decode(self, obj, context, path): def _decode(self, obj, context, path):
return swap_nibbles(b2h(obj)) return swap_nibbles(b2h(obj))
def _encode(self, obj, context, path): def _encode(self, obj, context, path):
return h2b(swap_nibbles(obj)) return h2b(swap_nibbles(obj))
class Rpad(Adapter): class Rpad(Adapter):
""" """
Encoder appends padding bytes (b'\\xff') up to target size. Encoder appends padding bytes (b'\\xff') up to target size.
@ -54,9 +62,11 @@ class Rpad(Adapter):
def _encode(self, obj, context, path): def _encode(self, obj, context, path):
if len(obj) > self.sizeof(): 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)) return obj + self.pattern * (self.sizeof() - len(obj))
class GsmStringAdapter(Adapter): class GsmStringAdapter(Adapter):
"""Convert GSM 03.38 encoded bytes to a string.""" """Convert GSM 03.38 encoded bytes to a string."""
@ -71,6 +81,7 @@ class GsmStringAdapter(Adapter):
def _encode(self, obj, context, path): def _encode(self, obj, context, path):
return obj.encode(self.codec, self.err) return obj.encode(self.codec, self.err)
def filter_dict(d, exclude_prefix='_'): def filter_dict(d, exclude_prefix='_'):
"""filter the input dict to ensure no keys starting with 'exclude_prefix' remain.""" """filter the input dict to ensure no keys starting with 'exclude_prefix' remain."""
if not isinstance(d, dict): if not isinstance(d, dict):
@ -85,8 +96,6 @@ def filter_dict(d, exclude_prefix='_'):
res[key] = value res[key] = value
return res return res
from construct.lib.containers import Container, ListContainer
from construct.core import EnumIntegerString
def normalize_construct(c): def normalize_construct(c):
"""Convert a construct specific type to a related base type, mostly useful """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 # in the dict: '_io': <_io.BytesIO object at 0x7fdb64e05860> which we cannot json-serialize
c = filter_dict(c) c = filter_dict(c)
if isinstance(c, Container) or isinstance(c, dict): 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): elif isinstance(c, ListContainer):
r = [normalize_construct(x) for x in c] r = [normalize_construct(x) for x in c]
elif isinstance(c, list): elif isinstance(c, list):
@ -106,13 +115,15 @@ def normalize_construct(c):
r = c r = c
return r 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().""" """Helper function to wrap around normalize_construct() and filter_dict()."""
if not length: if not length:
length = len(raw_bin_data) length = len(raw_bin_data)
parsed = c.parse(raw_bin_data, total_len=length) parsed = c.parse(raw_bin_data, total_len=length)
return normalize_construct(parsed) return normalize_construct(parsed)
# here we collect some shared / common definitions of data types # here we collect some shared / common definitions of data types
LV = Prefixed(Int8ub, HexAdapter(GreedyBytes)) 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 # Field that packs all remaining Reserved for Future Use (RFU) bytes
GreedyBytesRFU = Default(GreedyBytes, b'') GreedyBytesRFU = Default(GreedyBytes, b'')
def BitsRFU(n=1): def BitsRFU(n=1):
''' '''
Field that packs Reserved for Future Use (RFU) bit(s) Field that packs Reserved for Future Use (RFU) bit(s)
@ -143,6 +155,7 @@ def BitsRFU(n=1):
''' '''
return Default(BitsInteger(n), __RFU_VALUE) return Default(BitsInteger(n), __RFU_VALUE)
def BytesRFU(n=1): def BytesRFU(n=1):
''' '''
Field that packs Reserved for Future Use (RFU) byte(s) Field that packs Reserved for Future Use (RFU) byte(s)
@ -157,6 +170,7 @@ def BytesRFU(n=1):
''' '''
return Default(Bytes(n), __RFU_VALUE) return Default(Bytes(n), __RFU_VALUE)
def GsmString(n): def GsmString(n):
''' '''
GSM 03.38 encoded byte string of fixed length 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/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
# #
class NoCardError(Exception): class NoCardError(Exception):
"""No card was found in the reader.""" """No card was found in the reader."""
pass pass
class ProtocolError(Exception): class ProtocolError(Exception):
"""Some kind of protocol level error interfacing with the card.""" """Some kind of protocol level error interfacing with the card."""
pass pass
class ReaderError(Exception): class ReaderError(Exception):
"""Some kind of general error with the card reader.""" """Some kind of general error with the card reader."""
pass pass
class SwMatchError(Exception): class SwMatchError(Exception):
"""Raised when an operation specifies an expected SW but the actual SW from """Raised when an operation specifies an expected SW but the actual SW from
the card doesn't match.""" the card doesn't match."""
def __init__(self, sw_actual:str, sw_expected:str, rs=None):
""" 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) Args:
sw_expected : the SW we expected to receive from the card (4 hex digits) sw_actual : the SW we actually received from the card (4 hex digits)
rs : interpreter class to convert SW to string 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.sw_actual = sw_actual
self.rs = rs self.sw_expected = sw_expected
def __str__(self): self.rs = rs
if self.rs:
r = self.rs.interpret_sw(self.sw_actual) def __str__(self):
if r: if self.rs:
return "SW match failed! Expected %s and got %s: %s - %s" % (self.sw_expected, self.sw_actual, r[0], r[1]) r = self.rs.interpret_sw(self.sw_actual)
return "SW match failed! Expected %s and got %s." % (self.sw_expected, 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.jsonpath import js_path_find, js_path_modify
from pySim.commands import SimCardCommands from pySim.commands import SimCardCommands
class CardFile(object): class CardFile(object):
"""Base class for all objects in the smart card filesystem. """Base class for all objects in the smart card filesystem.
Serve as a common ancestor to all other file types; rarely used directly. Serve as a common ancestor to all other file types; rarely used directly.
@ -51,8 +52,8 @@ class CardFile(object):
RESERVED_NAMES = ['..', '.', '/', 'MF'] RESERVED_NAMES = ['..', '.', '/', 'MF']
RESERVED_FIDS = ['3f00'] RESERVED_FIDS = ['3f00']
def __init__(self, fid:str=None, sfid:str=None, name:str=None, desc:str=None, def __init__(self, fid: str = None, sfid: str = None, name: str = None, desc: str = None,
parent:Optional['CardDF']=None, profile:Optional['CardProfile']=None): parent: Optional['CardDF'] = None, profile: Optional['CardProfile'] = None):
""" """
Args: Args:
fid : File Identifier (4 hex digits) fid : File Identifier (4 hex digits)
@ -74,11 +75,11 @@ class CardFile(object):
if self.parent and self.parent != self and self.fid: if self.parent and self.parent != self and self.fid:
self.parent.add_file(self) self.parent.add_file(self)
self.profile = profile 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 # Note: the basic properties (fid, name, ect.) are verified when
# the file is attached to a parent file. See method add_file() in # the file is attached to a parent file. See method add_file() in
# class Card DF # class Card DF
def __str__(self): def __str__(self):
if self.name: if self.name:
@ -86,13 +87,13 @@ class CardFile(object):
else: else:
return self.fid 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: if prefer_name and self.name:
return self.name return self.name
else: else:
return self.fid 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. """Return fully qualified path to file as list of FID or name strings.
Args: Args:
@ -117,7 +118,7 @@ class CardFile(object):
node = node.parent node = node.parent
return cast(CardMF, node) 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. """Return a dict of {'identifier': self} tuples.
Args: Args:
@ -136,7 +137,7 @@ class CardFile(object):
sels.update({self.name: self}) sels.update({self.name: self})
return sels 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. """Return a dict of {'identifier': File} that is selectable from the current file.
Args: Args:
@ -158,11 +159,11 @@ class CardFile(object):
if flags == [] or 'MF' in flags: if flags == [] or 'MF' in flags:
mf = self.get_mf() mf = self.get_mf()
if mf: if mf:
sels.update(mf._get_self_selectables(flags = flags)) sels.update(mf._get_self_selectables(flags=flags))
sels.update(mf.get_app_selectables(flags = flags)) sels.update(mf.get_app_selectables(flags=flags))
return sels 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. """Return a dict of {'identifier': File} that is selectable from the current file.
Args: Args:
@ -176,18 +177,18 @@ class CardFile(object):
sel_keys.sort() sel_keys.sort()
return sel_keys 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. """Decode the response to a SELECT command.
Args: 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, # 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 # 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. # 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 # This way we recursively travel up the file system tree until we hit a file
# that does implement a concrete decoder. # that does implement a concrete decoder.
if self.parent: if self.parent:
return self.parent.decode_select_response(data_hex) return self.parent.decode_select_response(data_hex)
@ -206,6 +207,7 @@ class CardFile(object):
return self.parent.get_profile() return self.parent.get_profile()
return None return None
class CardDF(CardFile): class CardDF(CardFile):
"""DF (Dedicated File) in the smart card filesystem. Those are basically sub-directories.""" """DF (Dedicated File) in the smart card filesystem. Those are basically sub-directories."""
@ -225,7 +227,7 @@ class CardDF(CardFile):
def __str__(self): def __str__(self):
return "DF(%s)" % (super().__str__()) 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. """Add a child (DF/EF) to this DF.
Args: Args:
child: The new DF/EF to be added child: The new DF/EF to be added
@ -233,7 +235,7 @@ class CardDF(CardFile):
""" """
if not isinstance(child, CardFile): if not isinstance(child, CardFile):
raise TypeError("Expected a File instance") 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)) raise ValueError("File name %s is not a valid fid" % (child.fid))
if child.name in CardFile.RESERVED_NAMES: if child.name in CardFile.RESERVED_NAMES:
raise ValueError("File name %s is a reserved name" % (child.name)) 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 child.fid in self.children:
if ignore_existing: if ignore_existing:
return 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): 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 self.lookup_file_by_name(child.name):
if ignore_existing: if ignore_existing:
return 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 self.children[child.fid] = child
child.parent = self 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 """Add a list of child (DF/EF) to this DF
Args: Args:
@ -262,7 +267,7 @@ class CardDF(CardFile):
for child in children: for child in children:
self.add_file(child, ignore_existing) 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. """Return a dict of {'identifier': File} that is selectable from the current DF.
Args: Args:
@ -275,12 +280,12 @@ class CardDF(CardFile):
# global selectables + our children # global selectables + our children
sels = super().get_selectables(flags) sels = super().get_selectables(flags)
if flags == [] or 'FIDS' in 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: 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 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.""" """Find a file with given name within current DF."""
if name == None: if name == None:
return None return None
@ -289,7 +294,7 @@ class CardDF(CardFile):
return i return i
return None 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.""" """Find a file with given short file ID within current DF."""
if sfid == None: if sfid == None:
return None return None
@ -298,7 +303,7 @@ class CardDF(CardFile):
return i return i
return None 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.""" """Find a file with given file ID within current DF."""
if fid in self.children: if fid in self.children:
return self.children[fid] return self.children[fid]
@ -307,6 +312,7 @@ class CardDF(CardFile):
class CardMF(CardDF): class CardMF(CardDF):
"""MF (Master File) in the smart card filesystem""" """MF (Master File) in the smart card filesystem"""
def __init__(self, **kwargs): def __init__(self, **kwargs):
# can be overridden; use setdefault # can be overridden; use setdefault
kwargs.setdefault('fid', '3f00') kwargs.setdefault('fid', '3f00')
@ -320,20 +326,20 @@ class CardMF(CardDF):
def __str__(self): def __str__(self):
return "MF(%s)" % (self.fid) 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""" """Add an Application to the MF"""
if not isinstance(app, CardADF): if not isinstance(app, CardADF):
raise TypeError("Expected an ADF instance") raise TypeError("Expected an ADF instance")
if app.aid in self.applications: if app.aid in self.applications:
raise ValueError("AID %s already exists" % (app.aid)) raise ValueError("AID %s already exists" % (app.aid))
self.applications[app.aid] = app self.applications[app.aid] = app
app.parent=self app.parent = self
def get_app_names(self): def get_app_names(self):
"""Get list of completions (AID names)""" """Get list of completions (AID names)"""
return [x.name for x in self.applications] 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. """Return a dict of {'identifier': File} that is selectable from the current DF.
Args: Args:
@ -347,22 +353,23 @@ class CardMF(CardDF):
sels.update(self.get_app_selectables(flags)) sels.update(self.get_app_selectables(flags))
return sels return sels
def get_app_selectables(self, flags = []) -> dict: def get_app_selectables(self, flags=[]) -> dict:
"""Get applications by AID + name""" """Get applications by AID + name"""
sels = {} sels = {}
if flags == [] or 'AIDS' in flags: 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: 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 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. """Decode the response to a SELECT command.
This is the fall-back method which automatically defers to the standard decoding 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 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 performed. Specific derived classes (usually ADF) can overload this method to
install specific decoding. install specific decoding.
""" """
profile = self.get_profile() profile = self.get_profile()
@ -372,9 +379,11 @@ class CardMF(CardDF):
else: else:
return data_hex return data_hex
class CardADF(CardDF): class CardADF(CardDF):
"""ADF (Application Dedicated File) in the smart card filesystem""" """ADF (Application Dedicated File) in the smart card filesystem"""
def __init__(self, aid:str, **kwargs):
def __init__(self, aid: str, **kwargs):
super().__init__(**kwargs) super().__init__(**kwargs)
# reference to CardApplication may be set from CardApplication constructor # reference to CardApplication may be set from CardApplication constructor
self.application = None # type: Optional[CardApplication] self.application = None # type: Optional[CardApplication]
@ -386,7 +395,7 @@ class CardADF(CardDF):
def __str__(self): def __str__(self):
return "ADF(%s)" % (self.aid) 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: if self.name and prefer_name:
return self.name return self.name
else: else:
@ -395,6 +404,7 @@ class CardADF(CardDF):
class CardEF(CardFile): class CardEF(CardFile):
"""EF (Entry File) in the smart card filesystem""" """EF (Entry File) in the smart card filesystem"""
def __init__(self, *, fid, **kwargs): def __init__(self, *, fid, **kwargs):
kwargs['fid'] = fid kwargs['fid'] = fid
super().__init__(**kwargs) super().__init__(**kwargs)
@ -402,7 +412,7 @@ class CardEF(CardFile):
def __str__(self): def __str__(self):
return "EF(%s)" % (super().__str__()) 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. """Return a dict of {'identifier': File} that is selectable from the current DF.
Args: Args:
@ -412,9 +422,10 @@ class CardEF(CardFile):
dict containing all selectable items. Key is identifier (string), value dict containing all selectable items. Key is identifier (string), value
a reference to a CardFile (or derived class) instance. 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 = 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 return sels
@ -427,12 +438,16 @@ class TransparentEF(CardEF):
@with_default_category('Transparent EF Commands') @with_default_category('Transparent EF Commands')
class ShellCommands(CommandSet): class ShellCommands(CommandSet):
"""Shell commands specific for transparent EFs.""" """Shell commands specific for transparent EFs."""
def __init__(self): def __init__(self):
super().__init__() super().__init__()
read_bin_parser = argparse.ArgumentParser() 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(
read_bin_parser.add_argument('--length', type=int, help='Number of bytes to read') '--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) @cmd2.with_argparser(read_bin_parser)
def do_read_binary(self, opts): def do_read_binary(self, opts):
"""Read binary data from a transparent EF""" """Read binary data from a transparent EF"""
@ -442,6 +457,7 @@ class TransparentEF(CardEF):
read_bin_dec_parser = argparse.ArgumentParser() read_bin_dec_parser = argparse.ArgumentParser()
read_bin_dec_parser.add_argument('--oneline', action='store_true', read_bin_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_bin_dec_parser) @cmd2.with_argparser(read_bin_dec_parser)
def do_read_binary_decoded(self, opts): def do_read_binary_decoded(self, opts):
"""Read + decode data from a transparent EF""" """Read + decode data from a transparent EF"""
@ -449,8 +465,11 @@ class TransparentEF(CardEF):
self._cmd.poutput_json(data, opts.oneline) self._cmd.poutput_json(data, opts.oneline)
upd_bin_parser = argparse.ArgumentParser() 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(
upd_bin_parser.add_argument('data', help='Data bytes (hex format) to write') '--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) @cmd2.with_argparser(upd_bin_parser)
def do_update_binary(self, opts): def do_update_binary(self, opts):
"""Update (Write) data of a transparent EF""" """Update (Write) data of a transparent EF"""
@ -459,15 +478,18 @@ class TransparentEF(CardEF):
self._cmd.poutput(data) self._cmd.poutput(data)
upd_bin_dec_parser = argparse.ArgumentParser() 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, upd_bin_dec_parser.add_argument('--json-path', type=str,
help='JSON path to modify specific element of file only') help='JSON path to modify specific element of file only')
@cmd2.with_argparser(upd_bin_dec_parser) @cmd2.with_argparser(upd_bin_dec_parser)
def do_update_binary_decoded(self, opts): def do_update_binary_decoded(self, opts):
"""Encode + Update (Write) data of a transparent EF""" """Encode + Update (Write) data of a transparent EF"""
if opts.json_path: if opts.json_path:
(data_json, sw) = self._cmd.rs.read_binary_dec() (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: else:
data_json = json.loads(opts.data) data_json = json.loads(opts.data)
(data, sw) = self._cmd.rs.update_binary_dec(data_json) (data, sw) = self._cmd.rs.update_binary_dec(data_json)
@ -493,9 +515,8 @@ class TransparentEF(CardEF):
if data: if data:
self._cmd.poutput_json(data) self._cmd.poutput_json(data)
def __init__(self, fid: str, sfid: str = None, name: str = None, desc: str = None, parent: CardDF = None,
def __init__(self, fid:str, sfid:str=None, name:str=None, desc:str=None, parent:CardDF=None, size={1, None}):
size={1,None}):
""" """
Args: Args:
fid : File Identifier (4 hex digits) fid : File Identifier (4 hex digits)
@ -511,7 +532,7 @@ class TransparentEF(CardEF):
self.size = size self.size = size
self.shell_commands = [self.ShellCommands()] 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. """Decode raw (binary) data into abstract representation.
A derived class would typically provide a _decode_bin() or _decode_hex() method 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 t.to_dict()
return {'raw': raw_bin_data.hex()} 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. """Decode raw (hex string) data into abstract representation.
A derived class would typically provide a _decode_bin() or _decode_hex() method 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 t.to_dict()
return {'raw': raw_bin_data.hex()} 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. """Encode abstract representation into raw (binary) data.
A derived class would typically provide an _encode_bin() or _encode_hex() method 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 = self._tlv() if inspect.isclass(self._tlv) else self._tlv
t.from_dict(abstract_data) t.from_dict(abstract_data)
return t.to_tlv() 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. """Encode abstract representation into raw (hex string) data.
A derived class would typically provide an _encode_bin() or _encode_hex() method 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 = self._tlv() if inspect.isclass(self._tlv) else self._tlv
t.from_dict(abstract_data) t.from_dict(abstract_data)
return b2h(t.to_tlv()) 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): class LinFixedEF(CardEF):
@ -627,12 +650,16 @@ class LinFixedEF(CardEF):
@with_default_category('Linear Fixed EF Commands') @with_default_category('Linear Fixed EF Commands')
class ShellCommands(CommandSet): class ShellCommands(CommandSet):
"""Shell commands specific for Linear Fixed EFs.""" """Shell commands specific for Linear Fixed EFs."""
def __init__(self): def __init__(self):
super().__init__() super().__init__()
read_rec_parser = argparse.ArgumentParser() 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(
read_rec_parser.add_argument('--count', type=int, default=1, help='Number of records to be read, beginning at record_nr') '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) @cmd2.with_argparser(read_rec_parser)
def do_read_record(self, opts): def do_read_record(self, opts):
"""Read one or multiple records from a record-oriented EF""" """Read one or multiple records from a record-oriented EF"""
@ -640,15 +667,17 @@ class LinFixedEF(CardEF):
recnr = opts.record_nr + r recnr = opts.record_nr + r
(data, sw) = self._cmd.rs.read_record(recnr) (data, sw) = self._cmd.rs.read_record(recnr)
if (len(data) > 0): if (len(data) > 0):
recstr = str(data) recstr = str(data)
else: else:
recstr = "(empty)" recstr = "(empty)"
self._cmd.poutput("%03d %s" % (recnr, recstr)) self._cmd.poutput("%03d %s" % (recnr, recstr))
read_rec_dec_parser = argparse.ArgumentParser() 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', read_rec_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_rec_dec_parser) @cmd2.with_argparser(read_rec_dec_parser)
def do_read_record_decoded(self, opts): def do_read_record_decoded(self, opts):
"""Read + decode a record from a record-oriented EF""" """Read + decode a record from a record-oriented EF"""
@ -656,6 +685,7 @@ class LinFixedEF(CardEF):
self._cmd.poutput_json(data, opts.oneline) self._cmd.poutput_json(data, opts.oneline)
read_recs_parser = argparse.ArgumentParser() read_recs_parser = argparse.ArgumentParser()
@cmd2.with_argparser(read_recs_parser) @cmd2.with_argparser(read_recs_parser)
def do_read_records(self, opts): def do_read_records(self, opts):
"""Read all records from a record-oriented EF""" """Read all records from a record-oriented EF"""
@ -663,14 +693,15 @@ class LinFixedEF(CardEF):
for recnr in range(1, 1 + num_of_rec): for recnr in range(1, 1 + num_of_rec):
(data, sw) = self._cmd.rs.read_record(recnr) (data, sw) = self._cmd.rs.read_record(recnr)
if (len(data) > 0): if (len(data) > 0):
recstr = str(data) recstr = str(data)
else: else:
recstr = "(empty)" recstr = "(empty)"
self._cmd.poutput("%03d %s" % (recnr, recstr)) self._cmd.poutput("%03d %s" % (recnr, recstr))
read_recs_dec_parser = argparse.ArgumentParser() read_recs_dec_parser = argparse.ArgumentParser()
read_recs_dec_parser.add_argument('--oneline', action='store_true', 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) @cmd2.with_argparser(read_recs_dec_parser)
def do_read_records_decoded(self, opts): def do_read_records_decoded(self, opts):
"""Read + decode all records from a record-oriented EF""" """Read + decode all records from a record-oriented EF"""
@ -683,8 +714,11 @@ class LinFixedEF(CardEF):
self._cmd.poutput_json(data_list, opts.oneline) self._cmd.poutput_json(data_list, opts.oneline)
upd_rec_parser = argparse.ArgumentParser() 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(
upd_rec_parser.add_argument('data', help='Data bytes (hex format) to write') '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) @cmd2.with_argparser(upd_rec_parser)
def do_update_record(self, opts): def do_update_record(self, opts):
"""Update (write) data to a record-oriented EF""" """Update (write) data to a record-oriented EF"""
@ -693,24 +727,31 @@ class LinFixedEF(CardEF):
self._cmd.poutput(data) self._cmd.poutput(data)
upd_rec_dec_parser = argparse.ArgumentParser() 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(
upd_rec_dec_parser.add_argument('data', help='Abstract data (JSON format) to write') '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, upd_rec_dec_parser.add_argument('--json-path', type=str,
help='JSON path to modify specific element of record only') help='JSON path to modify specific element of record only')
@cmd2.with_argparser(upd_rec_dec_parser) @cmd2.with_argparser(upd_rec_dec_parser)
def do_update_record_decoded(self, opts): def do_update_record_decoded(self, opts):
"""Encode + Update (write) data to a record-oriented EF""" """Encode + Update (write) data to a record-oriented EF"""
if opts.json_path: if opts.json_path:
(data_json, sw) = self._cmd.rs.read_record_dec(opts.record_nr) (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: else:
data_json = json.loads(opts.data) 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: if data:
self._cmd.poutput(data) self._cmd.poutput(data)
edit_rec_dec_parser = argparse.ArgumentParser() 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) @cmd2.with_argparser(edit_rec_dec_parser)
def do_edit_record_decoded(self, opts): def do_edit_record_decoded(self, opts):
"""Edit the JSON representation of one record in an editor.""" """Edit the JSON representation of one record in an editor."""
@ -727,13 +768,13 @@ class LinFixedEF(CardEF):
if edited_json == orig_json: if edited_json == orig_json:
self._cmd.poutput("Data not modified, skipping write") self._cmd.poutput("Data not modified, skipping write")
else: 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: if data:
self._cmd.poutput_json(data) self._cmd.poutput_json(data)
def __init__(self, fid: str, sfid: str = None, name: str = None, desc: str = None,
def __init__(self, fid:str, sfid:str=None, name:str=None, desc:str=None, parent: Optional[CardDF] = None, rec_len={1, None}):
parent:Optional[CardDF]=None, rec_len={1,None}):
""" """
Args: Args:
fid : File Identifier (4 hex digits) fid : File Identifier (4 hex digits)
@ -749,7 +790,7 @@ class LinFixedEF(CardEF):
self._construct = None self._construct = None
self._tlv = 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. """Decode raw (hex string) data into abstract representation.
A derived class would typically provide a _decode_record_bin() or _decode_record_hex() 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 t.to_dict()
return {'raw': raw_bin_data.hex()} 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. """Decode raw (binary) data into abstract representation.
A derived class would typically provide a _decode_record_bin() or _decode_record_hex() 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 t.to_dict()
return {'raw': raw_hex_data} 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. """Encode abstract representation into raw (hex string) data.
A derived class would typically provide an _encode_record_bin() or _encode_record_hex() 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 = self._tlv() if inspect.isclass(self._tlv) else self._tlv
t.from_dict(abstract_data) t.from_dict(abstract_data)
return b2h(t.to_tlv()) 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. """Encode abstract representation into raw (binary) data.
A derived class would typically provide an _encode_record_bin() or _encode_record_hex() 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 = self._tlv() if inspect.isclass(self._tlv) else self._tlv
t.from_dict(abstract_data) t.from_dict(abstract_data)
return t.to_tlv() 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): class CyclicEF(LinFixedEF):
"""Cyclic EF (Entry File) in the smart card filesystem""" """Cyclic EF (Entry File) in the smart card filesystem"""
# we don't really have any special support for those; just recycling LinFixedEF here # 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}): def __init__(self, fid: str, sfid: str = None, name: str = None, desc: str = None, parent: CardDF = None,
super().__init__(fid=fid, sfid=sfid, name=name, desc=desc, parent=parent, rec_len=rec_len) rec_len={1, None}):
super().__init__(fid=fid, sfid=sfid, name=name,
desc=desc, parent=parent, rec_len=rec_len)
class TransRecEF(TransparentEF): class TransRecEF(TransparentEF):
"""Transparent EF (Entry File) containing fixed-size records. """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 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. 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: Args:
fid : File Identifier (4 hex digits) 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) super().__init__(fid=fid, sfid=sfid, name=name, desc=desc, parent=parent, size=size)
self.rec_len = rec_len 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. """Decode raw (hex string) data into abstract representation.
A derived class would typically provide a _decode_record_bin() or _decode_record_hex() 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 t.to_dict()
return {'raw': raw_hex_data} 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. """Decode raw (binary) data into abstract representation.
A derived class would typically provide a _decode_record_bin() or _decode_record_hex() 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 t.to_dict()
return {'raw': raw_hex_data} 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. """Encode abstract representation into raw (hex string) data.
A derived class would typically provide an _encode_record_bin() or _encode_record_hex() 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 = self._tlv() if inspect.isclass(self._tlv) else self._tlv
t.from_dict(abstract_data) t.from_dict(abstract_data)
return b2h(t.to_tlv()) 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. """Encode abstract representation into raw (binary) data.
A derived class would typically provide an _encode_record_bin() or _encode_record_hex() 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 = self._tlv() if inspect.isclass(self._tlv) else self._tlv
t.from_dict(abstract_data) t.from_dict(abstract_data)
return t.to_tlv() 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): 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)] 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] return [self.decode_record_bin(x) for x in chunks]
def _encode_bin(self, abstract_data) -> bytes: def _encode_bin(self, abstract_data) -> bytes:
@ -1014,11 +1065,14 @@ class BerTlvEF(CardEF):
@with_default_category('BER-TLV EF Commands') @with_default_category('BER-TLV EF Commands')
class ShellCommands(CommandSet): class ShellCommands(CommandSet):
"""Shell commands specific for BER-TLV EFs.""" """Shell commands specific for BER-TLV EFs."""
def __init__(self): def __init__(self):
super().__init__() super().__init__()
retrieve_data_parser = argparse.ArgumentParser() 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) @cmd2.with_argparser(retrieve_data_parser)
def do_retrieve_data(self, opts): def do_retrieve_data(self, opts):
"""Retrieve (Read) data from a BER-TLV EF""" """Retrieve (Read) data from a BER-TLV EF"""
@ -1031,8 +1085,11 @@ class BerTlvEF(CardEF):
self._cmd.poutput(tags) self._cmd.poutput(tags)
set_data_parser = argparse.ArgumentParser() 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(
set_data_parser.add_argument('data', help='Data bytes (hex format) to write') '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) @cmd2.with_argparser(set_data_parser)
def do_set_data(self, opts): def do_set_data(self, opts):
"""Set (Write) data for a given tag in a BER-TLV EF""" """Set (Write) data for a given tag in a BER-TLV EF"""
@ -1041,7 +1098,9 @@ class BerTlvEF(CardEF):
self._cmd.poutput(data) self._cmd.poutput(data)
del_data_parser = argparse.ArgumentParser() 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) @cmd2.with_argparser(del_data_parser)
def do_delete_data(self, opts): def do_delete_data(self, opts):
"""Delete data for a given tag in a BER-TLV EF""" """Delete data for a given tag in a BER-TLV EF"""
@ -1049,9 +1108,8 @@ class BerTlvEF(CardEF):
if data: if data:
self._cmd.poutput(data) self._cmd.poutput(data)
def __init__(self, fid: str, sfid: str = None, name: str = None, desc: str = None, parent: CardDF = None,
def __init__(self, fid:str, sfid:str=None, name:str=None, desc:str=None, parent:CardDF=None, size={1, None}):
size={1,None}):
""" """
Args: Args:
fid : File Identifier (4 hex digits) fid : File Identifier (4 hex digits)
@ -1069,7 +1127,8 @@ class BerTlvEF(CardEF):
class RuntimeState(object): class RuntimeState(object):
"""Represent the runtime state of a session with a card.""" """Represent the runtime state of a session with a card."""
def __init__(self, card, profile:'CardProfile'):
def __init__(self, card, profile: 'CardProfile'):
""" """
Args: Args:
card : pysim.cards.Card instance card : pysim.cards.Card instance
@ -1077,12 +1136,13 @@ class RuntimeState(object):
""" """
self.mf = CardMF(profile=profile) self.mf = CardMF(profile=profile)
self.card = card self.card = card
self.selected_file = self.mf # type: CardDF self.selected_file = self.mf # type: CardDF
self.profile = profile self.profile = profile
# make sure the class and selection control bytes, which are specified # make sure the class and selection control bytes, which are specified
# by the card profile are used # 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 # add application ADFs + MF-files from profile
apps = self._match_applications() apps = self._match_applications()
@ -1170,7 +1230,7 @@ class RuntimeState(object):
node = node.parent node = node.parent
return None 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 """Interpret a given status word relative to the currently selected application
or the underlying card profile. or the underlying card profile.
@ -1191,11 +1251,12 @@ class RuntimeState(object):
res = app.interpret_sw(sw) res = app.interpret_sw(sw)
return res or self.profile.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 """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): 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: try:
(data, sw) = self.card._scc.select_file(fid) (data, sw) = self.card._scc.select_file(fid)
@ -1207,18 +1268,21 @@ class RuntimeState(object):
select_resp = self.selected_file.decode_select_response(data) select_resp = self.selected_file.decode_select_response(data)
if (select_resp['file_descriptor']['file_type'] == 'df'): 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: else:
if (select_resp['file_descriptor']['structure'] == 'transparent'): 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: 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.add_files([f])
self.selected_file = f self.selected_file = f
return select_resp 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, ...). """Select a file (EF, DF, ADF, MF, ...).
Args: Args:
@ -1265,14 +1329,14 @@ class RuntimeState(object):
(data, sw) = self.card._scc.status() (data, sw) = self.card._scc.status()
return self.selected_file.decode_select_response(data) 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.""" """Request ACTIVATE FILE of specified file."""
sels = self.selected_file.get_selectables() sels = self.selected_file.get_selectables()
f = sels[name] f = sels[name]
data, sw = self.card._scc.activate_file(f.fid) data, sw = self.card._scc.activate_file(f.fid)
return data, sw 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. """Read [part of] a transparent EF binary data.
Args: Args:
@ -1298,7 +1362,7 @@ class RuntimeState(object):
dec_data = self.selected_file.decode_hex(data) dec_data = self.selected_file.decode_hex(data)
return (dec_data, sw) 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. """Update transparent EF binary data.
Args: Args:
@ -1309,7 +1373,7 @@ class RuntimeState(object):
raise TypeError("Only works with TransparentEF") raise TypeError("Only works with TransparentEF")
return self.card._scc.update_binary(self.selected_file.fid, data_hex, offset, conserve=self.conserve_write) 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 """Update transparent EF from abstract data. Encodes the data to binary and
then updates the EF with it. then updates the EF with it.
@ -1319,7 +1383,7 @@ class RuntimeState(object):
data_hex = self.selected_file.encode_hex(data) data_hex = self.selected_file.encode_hex(data)
return self.update_binary(data_hex) 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. """Read a record as binary data.
Args: Args:
@ -1332,7 +1396,7 @@ class RuntimeState(object):
# returns a string of hex nibbles # returns a string of hex nibbles
return self.card._scc.read_record(self.selected_file.fid, rec_nr) 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. """Read a record and decode it to abstract data.
Args: Args:
@ -1343,7 +1407,7 @@ class RuntimeState(object):
(data, sw) = self.read_record(rec_nr) (data, sw) = self.read_record(rec_nr)
return (self.selected_file.decode_record_hex(data), sw) 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 """Update a record with given binary data
Args: Args:
@ -1354,7 +1418,7 @@ class RuntimeState(object):
raise TypeError("Only works with Linear Fixed EF") 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) 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 """Update a record with given abstract data. Will encode abstract to binary data
and then write it to the given record on the card. 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) data_hex = self.selected_file.encode_record_hex(data)
return self.update_record(rec_nr, data_hex) 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. """Read a DO/TLV as binary data.
Args: Args:
@ -1390,7 +1454,7 @@ class RuntimeState(object):
tag, length, value, remainder = bertlv_parse_one(h2b(data)) tag, length, value, remainder = bertlv_parse_one(h2b(data))
return list(value) 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 """Update a TLV/DO with given binary data
Args: Args:
@ -1408,15 +1472,15 @@ class RuntimeState(object):
cmd_app.unregister_command_set(c) cmd_app.unregister_command_set(c)
class FileData(object): class FileData(object):
"""Represent the runtime, on-card data.""" """Represent the runtime, on-card data."""
def __init__(self, fdesc): def __init__(self, fdesc):
self.desc = fdesc self.desc = fdesc
self.fcp = None self.fcp = None
def interpret_sw(sw_data:dict, sw:str): def interpret_sw(sw_data: dict, sw: str):
"""Interpret a given status word. """Interpret a given status word.
Args: Args:
@ -1435,10 +1499,12 @@ def interpret_sw(sw_data:dict, sw:str):
return (class_str, descr) return (class_str, descr)
return None return None
class CardApplication(object): class CardApplication(object):
"""A card application is represented by an ADF (with contained hierarchy) and optionally """A card application is represented by an ADF (with contained hierarchy) and optionally
some SW definitions.""" 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: Args:
adf : ADF name adf : ADF name
@ -1477,11 +1543,11 @@ class CardModel(abc.ABC):
@classmethod @classmethod
@abc.abstractmethod @abc.abstractmethod
def add_files(cls, rs:RuntimeState): def add_files(cls, rs: RuntimeState):
"""Add model specific files to given RuntimeState.""" """Add model specific files to given RuntimeState."""
@classmethod @classmethod
def match(cls, scc:SimCardCommands) -> bool: def match(cls, scc: SimCardCommands) -> bool:
"""Test if given card matches this model.""" """Test if given card matches this model."""
card_atr = scc.get_atr() card_atr = scc.get_atr()
for atr in cls._atrs: for atr in cls._atrs:
@ -1492,7 +1558,7 @@ class CardModel(abc.ABC):
return False return False
@staticmethod @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 """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' (by ATR or overriding the 'match' method). If so, call their 'add_files'
method.""" method."""

View File

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

View File

@ -24,39 +24,57 @@ from pySim.filesystem import *
from pySim.tlv import * from pySim.tlv import *
# Table 91 + Section 8.2.1.2 # Table 91 + Section 8.2.1.2
class ApplicationId(BER_TLV_IE, tag=0x4f): class ApplicationId(BER_TLV_IE, tag=0x4f):
_construct = GreedyBytes _construct = GreedyBytes
# Table 91 # Table 91
class ApplicationLabel(BER_TLV_IE, tag=0x50): class ApplicationLabel(BER_TLV_IE, tag=0x50):
_construct = GreedyBytes _construct = GreedyBytes
# Table 91 + Section 5.3.1.2 # Table 91 + Section 5.3.1.2
class FileReference(BER_TLV_IE, tag=0x51): class FileReference(BER_TLV_IE, tag=0x51):
_construct = GreedyBytes _construct = GreedyBytes
# Table 91 # Table 91
class CommandApdu(BER_TLV_IE, tag=0x52): class CommandApdu(BER_TLV_IE, tag=0x52):
_construct = GreedyBytes _construct = GreedyBytes
# Table 91 # Table 91
class DiscretionaryData(BER_TLV_IE, tag=0x53): class DiscretionaryData(BER_TLV_IE, tag=0x53):
_construct = GreedyBytes _construct = GreedyBytes
# Table 91 # Table 91
class DiscretionaryTemplate(BER_TLV_IE, tag=0x73): class DiscretionaryTemplate(BER_TLV_IE, tag=0x73):
_construct = GreedyBytes _construct = GreedyBytes
# Table 91 + RFC1738 / RFC2396 # Table 91 + RFC1738 / RFC2396
class URL(BER_TLV_IE, tag=0x5f50): class URL(BER_TLV_IE, tag=0x5f50):
_construct = GreedyString('ascii') _construct = GreedyString('ascii')
# Table 91 # Table 91
class ApplicationRelatedDOSet(BER_TLV_IE, tag=0x61): class ApplicationRelatedDOSet(BER_TLV_IE, tag=0x61):
_construct = GreedyBytes _construct = GreedyBytes
# Section 8.2.1.3 Application Template # Section 8.2.1.3 Application Template
class ApplicationTemplate(BER_TLV_IE, tag=0x61, nested=[ApplicationId, ApplicationLabel, FileReference, class ApplicationTemplate(BER_TLV_IE, tag=0x61, nested=[ApplicationId, ApplicationLabel, FileReference,
CommandApdu, DiscretionaryData, DiscretionaryTemplate,URL, CommandApdu, DiscretionaryData, DiscretionaryTemplate, URL,
ApplicationRelatedDOSet]): ApplicationRelatedDOSet]):
pass 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 # You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
def js_path_find(js_dict, js_path): def js_path_find(js_dict, js_path):
"""Find/Match a JSON path within a given JSON-serializable dict. """Find/Match a JSON path within a given JSON-serializable dict.
Args: Args:
@ -35,6 +36,7 @@ def js_path_find(js_dict, js_path):
jsonpath_expr = jsonpath_ng.parse(js_path) jsonpath_expr = jsonpath_ng.parse(js_path)
return jsonpath_expr.find(js_dict) return jsonpath_expr.find(js_dict)
def js_path_modify(js_dict, js_path, new_val): def js_path_modify(js_dict, js_path, new_val):
"""Find/Match a JSON path within a given JSON-serializable dict. """Find/Match a JSON path within a given JSON-serializable dict.
Args: Args:
@ -45,4 +47,3 @@ def js_path_modify(js_dict, js_path, new_val):
jsonpath_expr = jsonpath_ng.parse(js_path) jsonpath_expr = jsonpath_ng.parse(js_path)
jsonpath_expr.find(js_dict) jsonpath_expr.find(js_dict)
jsonpath_expr.update(js_dict, new_val) jsonpath_expr.update(js_dict, new_val)

View File

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

View File

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

View File

@ -32,6 +32,7 @@ from pySim.exceptions import *
import inspect import inspect
import abc import abc
class TlvMeta(abc.ABCMeta): class TlvMeta(abc.ABCMeta):
"""Metaclass which we use to set some class variables at the time of defining a subclass. """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 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 x.nested_collection_cls = cls
return x return x
class TlvCollectionMeta(abc.ABCMeta): class TlvCollectionMeta(abc.ABCMeta):
"""Metaclass which we use to set some class variables at the time of defining a subclass. """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 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 a derived class' _construct variable, or
* via a 'construct' object stored in an instance _construct variable, or * via a 'construct' object stored in an instance _construct variable, or
* via a derived class' _{to,from}_bytes() methods.""" * via a derived class' _{to,from}_bytes() methods."""
def __init__(self): def __init__(self):
self.encoded = None self.encoded = None
self.decoded = None self.decoded = None
@ -95,7 +98,7 @@ class Transcodable(abc.ABC):
def _to_bytes(self): def _to_bytes(self):
raise NotImplementedError 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 """Convert from binary bytes to internal representation. Store the decoded result
in the internal state and return it.""" in the internal state and return it."""
self.encoded = do self.encoded = do
@ -110,9 +113,10 @@ class Transcodable(abc.ABC):
return self.decoded return self.decoded
# not an abstractmethod, as it is only required if no _construct exists # 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 raise NotImplementedError
class IE(Transcodable, metaclass=TlvMeta): class IE(Transcodable, metaclass=TlvMeta):
# we specify the metaclass so any downstream subclasses will automatically use it # 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 """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 v = self.decoded
return {type(self).__name__: v} 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. """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 this is a nested IE, the child IE instance list is re-created."""
if self.nested_collection: if self.nested_collection:
@ -177,7 +181,7 @@ class IE(Transcodable, metaclass=TlvMeta):
else: else:
return super().to_bytes() 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.""" """Parse _the value part_ from binary bytes to internal representation."""
if self.nested_collection: if self.nested_collection:
self.children = self.nested_collection.from_bytes(do) self.children = self.nested_collection.from_bytes(do)
@ -188,6 +192,7 @@ class IE(Transcodable, metaclass=TlvMeta):
class TLV_IE(IE): class TLV_IE(IE):
"""Abstract base class for various TLV type Information Elements.""" """Abstract base class for various TLV type Information Elements."""
def __init__(self, **kwargs): def __init__(self, **kwargs):
super().__init__(**kwargs) super().__init__(**kwargs)
@ -197,12 +202,12 @@ class TLV_IE(IE):
@classmethod @classmethod
@abc.abstractmethod @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.""" """Obtain the raw TAG at the start of the bytes provided by the user."""
@classmethod @classmethod
@abc.abstractmethod @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.""" """Obtain the length encoded at the start of the bytes provided by the user."""
@abc.abstractmethod @abc.abstractmethod
@ -210,7 +215,7 @@ class TLV_IE(IE):
"""Encode the tag part. Must be provided by derived (TLV format specific) class.""" """Encode the tag part. Must be provided by derived (TLV format specific) class."""
@abc.abstractmethod @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 """Encode the length part assuming a certain binary value. Must be provided by
derived (TLV format specific) class.""" derived (TLV format specific) class."""
@ -222,7 +227,7 @@ class TLV_IE(IE):
val = self.to_bytes() val = self.to_bytes()
return self._encode_tag() + self._encode_len(val) + val 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) (rawtag, remainder) = self.__class__._parse_tag_raw(do)
if rawtag: if rawtag:
if rawtag != self.tag: if rawtag != self.tag:
@ -240,50 +245,52 @@ class TLV_IE(IE):
class BER_TLV_IE(TLV_IE): class BER_TLV_IE(TLV_IE):
"""TLV_IE formatted as ASN.1 BER described in ITU-T X.690 8.1.2.""" """TLV_IE formatted as ASN.1 BER described in ITU-T X.690 8.1.2."""
def __init__(self, **kwargs): def __init__(self, **kwargs):
super().__init__(**kwargs) super().__init__(**kwargs)
@classmethod @classmethod
def _decode_tag(cls, do:bytes) -> Tuple[dict, bytes]: def _decode_tag(cls, do: bytes) -> Tuple[dict, bytes]:
return bertlv_parse_tag(do) return bertlv_parse_tag(do)
@classmethod @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) return bertlv_parse_tag_raw(do)
@classmethod @classmethod
def _parse_len(cls, do:bytes) -> Tuple[int, bytes]: def _parse_len(cls, do: bytes) -> Tuple[int, bytes]:
return bertlv_parse_len(do) return bertlv_parse_len(do)
def _encode_tag(self) -> bytes: def _encode_tag(self) -> bytes:
return bertlv_encode_tag(self._compute_tag()) 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)) return bertlv_encode_len(len(val))
class COMPR_TLV_IE(TLV_IE): class COMPR_TLV_IE(TLV_IE):
"""TLV_IE formated as COMPREHENSION-TLV as described in ETSI TS 101 220.""" """TLV_IE formated as COMPREHENSION-TLV as described in ETSI TS 101 220."""
def __init__(self, **kwargs): def __init__(self, **kwargs):
super().__init__(**kwargs) super().__init__(**kwargs)
self.comprehension = False self.comprehension = False
@classmethod @classmethod
def _decode_tag(cls, do:bytes) -> Tuple[dict, bytes]: def _decode_tag(cls, do: bytes) -> Tuple[dict, bytes]:
return comprehensiontlv_parse_tag(do) return comprehensiontlv_parse_tag(do)
@classmethod @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) return comprehensiontlv_parse_tag_raw(do)
@classmethod @classmethod
def _parse_len(cls, do:bytes) -> Tuple[int, bytes]: def _parse_len(cls, do: bytes) -> Tuple[int, bytes]:
return bertlv_parse_len(do) return bertlv_parse_len(do)
def _encode_tag(self) -> bytes: def _encode_tag(self) -> bytes:
return comprehensiontlv_encode_tag(self._compute_tag()) 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)) return bertlv_encode_len(len(val))
@ -294,14 +301,15 @@ class TLV_IE_Collection(metaclass=TlvCollectionMeta):
of each DO.""" of each DO."""
# this is overridden by the TlvCollectionMeta metaclass, if it is used to create subclasses # this is overridden by the TlvCollectionMeta metaclass, if it is used to create subclasses
possible_nested = [] possible_nested = []
def __init__(self, desc=None, **kwargs): def __init__(self, desc=None, **kwargs):
self.desc = desc self.desc = desc
#print("possible_nested: ", self.possible_nested) #print("possible_nested: ", self.possible_nested)
self.members = kwargs.get('nested', self.possible_nested) self.members = kwargs.get('nested', self.possible_nested)
self.members_by_tag = {} self.members_by_tag = {}
self.members_by_name = {} self.members_by_name = {}
self.members_by_tag = { m.tag: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 } 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 # if we are a constructed IE, [ordered] list of actual child-IE instances
self.children = kwargs.get('children', []) self.children = kwargs.get('children', [])
self.encoded = None self.encoded = None
@ -322,11 +330,11 @@ class TLV_IE_Collection(metaclass=TlvCollectionMeta):
return TLV_IE_Collection(self.desc, nested=members) return TLV_IE_Collection(self.desc, nested=members)
elif inspect.isclass(other) and issubclass(other, TLV_IE): elif inspect.isclass(other) and issubclass(other, TLV_IE):
# adding a member to a collection # 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: else:
raise TypeError 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. """Create a list of TLV_IEs from the collection based on binary input data.
Args: Args:
binary : binary bytes of encoded data binary : binary bytes of encoded data
@ -353,9 +361,9 @@ class TLV_IE_Collection(metaclass=TlvCollectionMeta):
else: else:
# unknown tag; create the related class on-the-fly using the same base class # unknown tag; create the related class on-the-fly using the same base class
name = 'unknown_%s_%X' % (first.__base__.__name__, tag) name = 'unknown_%s_%X' % (first.__base__.__name__, tag)
cls = type(name, (first.__base__,), {'tag':tag, 'possible_nested':[], cls = type(name, (first.__base__,), {'tag': tag, 'possible_nested': [],
'nested_collection_cls':None}) 'nested_collection_cls': None})
cls._from_bytes = lambda s, a : {'raw': a.hex()} cls._from_bytes = lambda s, a: {'raw': a.hex()}
cls._to_bytes = lambda s: bytes.fromhex(s.decoded['raw']) cls._to_bytes = lambda s: bytes.fromhex(s.decoded['raw'])
# create an instance and parse accordingly # create an instance and parse accordingly
inst = cls() inst = cls()
@ -364,7 +372,7 @@ class TLV_IE_Collection(metaclass=TlvCollectionMeta):
self.children = res self.children = res
return 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 """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.""" 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 # 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/>. # 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): class ApduTracer:
pass def trace_command(self, cmd):
pass
def trace_response(self, cmd, sw, resp):
pass
class LinkBase(abc.ABC): 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): def __init__(self, sw_interpreter=None, apdu_tracer=None):
self.sw_interpreter = sw_interpreter self.sw_interpreter = sw_interpreter
self.apdu_tracer = apdu_tracer self.apdu_tracer = apdu_tracer
@abc.abstractmethod @abc.abstractmethod
def _send_apdu_raw(self, pdu:str) -> Tuple[str, str]: def _send_apdu_raw(self, pdu: str) -> Tuple[str, str]:
"""Implementation specific method for sending the PDU.""" """Implementation specific method for sending the PDU."""
def set_sw_interpreter(self, interp): def set_sw_interpreter(self, interp):
"""Set an (optional) status word interpreter.""" """Set an (optional) status word interpreter."""
self.sw_interpreter = interp self.sw_interpreter = interp
@abc.abstractmethod @abc.abstractmethod
def wait_for_card(self, timeout:int=None, newcardonly:bool=False): def wait_for_card(self, timeout: int = None, newcardonly: bool = False):
"""Wait for a card and connect to it """Wait for a card and connect to it
Args: Args:
timeout : Maximum wait time in seconds (None=no timeout) timeout : Maximum wait time in seconds (None=no timeout)
newcardonly : Should we wait for a new card, or an already inserted one ? newcardonly : Should we wait for a new card, or an already inserted one ?
""" """
@abc.abstractmethod @abc.abstractmethod
def connect(self): def connect(self):
"""Connect to a card immediately """Connect to a card immediately
""" """
@abc.abstractmethod @abc.abstractmethod
def disconnect(self): def disconnect(self):
"""Disconnect from card """Disconnect from card
""" """
@abc.abstractmethod @abc.abstractmethod
def reset_card(self): def reset_card(self):
"""Resets the card (power down/up) """Resets the card (power down/up)
""" """
def send_apdu_raw(self, pdu:str): def send_apdu_raw(self, pdu: str):
"""Sends an APDU with minimal processing """Sends an APDU with minimal processing
Args: Args:
pdu : string of hexadecimal characters (ex. "A0A40000023F00") pdu : string of hexadecimal characters (ex. "A0A40000023F00")
Returns: Returns:
tuple(data, sw), where tuple(data, sw), where
data : string (in hex) of returned data (ex. "074F4EFFFF") data : string (in hex) of returned data (ex. "074F4EFFFF")
sw : string (in hex) of status word (ex. "9000") sw : string (in hex) of status word (ex. "9000")
""" """
if self.apdu_tracer: if self.apdu_tracer:
self.apdu_tracer.trace_command(pdu) self.apdu_tracer.trace_command(pdu)
(data, sw) = self._send_apdu_raw(pdu) (data, sw) = self._send_apdu_raw(pdu)
if self.apdu_tracer: if self.apdu_tracer:
self.apdu_tracer.trace_response(pdu, sw, data) self.apdu_tracer.trace_response(pdu, sw, data)
return (data, sw) return (data, sw)
def send_apdu(self, pdu): def send_apdu(self, pdu):
"""Sends an APDU and auto fetch response data """Sends an APDU and auto fetch response data
Args: Args:
pdu : string of hexadecimal characters (ex. "A0A40000023F00") pdu : string of hexadecimal characters (ex. "A0A40000023F00")
Returns: Returns:
tuple(data, sw), where tuple(data, sw), where
data : string (in hex) of returned data (ex. "074F4EFFFF") data : string (in hex) of returned data (ex. "074F4EFFFF")
sw : string (in hex) of status word (ex. "9000") sw : string (in hex) of status word (ex. "9000")
""" """
data, sw = self.send_apdu_raw(pdu) data, sw = self.send_apdu_raw(pdu)
# When whe have sent the first APDU, the SW may indicate that there are response bytes # 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 # available. There are two SWs commonly used for this 9fxx (sim) and 61xx (usim), where
# xx is the number of response bytes available. # xx is the number of response bytes available.
# See also: # See also:
if (sw is not None): if (sw is not None):
if ((sw[0:2] == '9f') or (sw[0:2] == '61')): 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=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 # 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] pdu_gr = pdu[0:2] + 'c00000' + sw[2:4]
data, sw = self.send_apdu_raw(pdu_gr) data, sw = self.send_apdu_raw(pdu_gr)
if sw[0:2] == '6c': if sw[0:2] == '6c':
# SW1=6C: ETSI TS 102 221 Table 7.1: Procedure byte coding # SW1=6C: ETSI TS 102 221 Table 7.1: Procedure byte coding
pdu_gr = pdu[0:8] + sw[2:4] pdu_gr = pdu[0:8] + sw[2:4]
data,sw = self.send_apdu_raw(pdu_gr) data, sw = self.send_apdu_raw(pdu_gr)
return data, sw return data, sw
def send_apdu_checksw(self, pdu, sw="9000"): def send_apdu_checksw(self, pdu, sw="9000"):
"""Sends an APDU and check returned SW """Sends an APDU and check returned SW
Args: Args:
pdu : string of hexadecimal characters (ex. "A0A40000023F00") pdu : string of hexadecimal characters (ex. "A0A40000023F00")
sw : string of 4 hexadecimal characters (ex. "9000"). The user may mask out certain sw : string of 4 hexadecimal characters (ex. "9000"). The user may mask out certain
digits using a '?' to add some ambiguity if needed. digits using a '?' to add some ambiguity if needed.
Returns: Returns:
tuple(data, sw), where tuple(data, sw), where
data : string (in hex) of returned data (ex. "074F4EFFFF") data : string (in hex) of returned data (ex. "074F4EFFFF")
sw : string (in hex) of status word (ex. "9000") sw : string (in hex) of status word (ex. "9000")
""" """
rv = self.send_apdu(pdu) rv = self.send_apdu(pdu)
if sw == '9000' and sw_match(rv[1], '91xx'): if sw == '9000' and sw_match(rv[1], '91xx'):
# proactive sim as per TS 102 221 Setion 7.4.2 # proactive sim as per TS 102 221 Setion 7.4.2
rv = self.send_apdu_checksw('80120000' + rv[1][2:], sw) rv = self.send_apdu_checksw('80120000' + rv[1][2:], sw)
print("FETCH: %s", rv[0]) print("FETCH: %s", rv[0])
if not sw_match(rv[1], sw): if not sw_match(rv[1], sw):
raise SwMatchError(rv[1], sw.lower(), self.sw_interpreter) raise SwMatchError(rv[1], sw.lower(), self.sw_interpreter)
return rv return rv
def send_apdu_constr(self, cla, ins, p1, p2, cmd_constr, cmd_data, resp_constr): 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. """Build and sends an APDU using a 'construct' definition; parses response.
Args: Args:
cla : string (in hex) ISO 7816 class byte cla : string (in hex) ISO 7816 class byte
ins : string (in hex) ISO 7816 instruction byte ins : string (in hex) ISO 7816 instruction byte
p1 : string (in hex) ISO 7116 Parameter 1 byte p1 : string (in hex) ISO 7116 Parameter 1 byte
p2 : string (in hex) ISO 7116 Parameter 2 byte p2 : string (in hex) ISO 7116 Parameter 2 byte
cmd_cosntr : defining how to generate binary APDU command data cmd_cosntr : defining how to generate binary APDU command data
cmd_data : command data passed to cmd_constr cmd_data : command data passed to cmd_constr
resp_cosntr : defining how to decode binary APDU response data resp_cosntr : defining how to decode binary APDU response data
Returns: Returns:
Tuple of (decoded_data, sw) Tuple of (decoded_data, sw)
""" """
cmd = cmd_constr.build(cmd_data) if cmd_data else '' cmd = cmd_constr.build(cmd_data) if cmd_data else ''
p3 = i2h([len(cmd)]) p3 = i2h([len(cmd)])
pdu = ''.join([cla, ins, p1, p2, p3, b2h(cmd)]) pdu = ''.join([cla, ins, p1, p2, p3, b2h(cmd)])
(data, sw) = self.send_apdu(pdu) (data, sw) = self.send_apdu(pdu)
if data: if data:
# filter the resulting dict to avoid '_io' members inside # filter the resulting dict to avoid '_io' members inside
rsp = filter_dict(resp_constr.parse(h2b(data))) rsp = filter_dict(resp_constr.parse(h2b(data)))
else: else:
rsp = None rsp = None
return (rsp, sw) return (rsp, sw)
def send_apdu_constr_checksw(self, cla, ins, p1, p2, cmd_constr, cmd_data, resp_constr, def send_apdu_constr_checksw(self, cla, ins, p1, p2, cmd_constr, cmd_data, resp_constr,
sw_exp="9000"): sw_exp="9000"):
"""Build and sends an APDU using a 'construct' definition; parses response. """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): def argparse_add_reader_args(arg_parser):
"""Add all reader related arguments to the given argparse.Argumentparser instance.""" """Add all reader related arguments to the given argparse.Argumentparser instance."""
serial_group = arg_parser.add_argument_group('Serial Reader') serial_group = arg_parser.add_argument_group('Serial Reader')
serial_group.add_argument('-d', '--device', metavar='DEV', default='/dev/ttyUSB0', serial_group.add_argument('-d', '--device', metavar='DEV', default='/dev/ttyUSB0',
help='Serial Device for SIM access') help='Serial Device for SIM access')
serial_group.add_argument('-b', '--baud', dest='baudrate', type=int, metavar='BAUD', default=9600, serial_group.add_argument('-b', '--baud', dest='baudrate', type=int, metavar='BAUD', default=9600,
help='Baud rate used for SIM access') help='Baud rate used for SIM access')
pcsc_group = arg_parser.add_argument_group('PC/SC Reader') 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, 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') help='PC/SC reader number to use for SIM access')
modem_group = arg_parser.add_argument_group('AT Command Modem Reader') modem_group = arg_parser.add_argument_group('AT Command Modem Reader')
modem_group.add_argument('--modem-device', dest='modem_dev', metavar='DEV', default=None, 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)') 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, modem_group.add_argument('--modem-baud', type=int, metavar='BAUD', default=115200,
help='Baud rate used for modem port') help='Baud rate used for modem port')
osmobb_group = arg_parser.add_argument_group('OsmocomBB Reader') osmobb_group = arg_parser.add_argument_group('OsmocomBB Reader')
osmobb_group.add_argument('--osmocon', dest='osmocon_sock', metavar='PATH', default=None, 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)') 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]: def init_reader(opts, **kwargs) -> Optional[LinkBase]:
""" """
Init card reader driver Init card reader driver
""" """
sl = None # type : :Optional[LinkBase] sl = None # type : :Optional[LinkBase]
try: try:
if opts.pcsc_dev is not None: if opts.pcsc_dev is not None:
print("Using PC/SC reader interface") print("Using PC/SC reader interface")
from pySim.transport.pcsc import PcscSimLink from pySim.transport.pcsc import PcscSimLink
sl = PcscSimLink(opts.pcsc_dev, **kwargs) sl = PcscSimLink(opts.pcsc_dev, **kwargs)
elif opts.osmocon_sock is not None: elif opts.osmocon_sock is not None:
print("Using Calypso-based (OsmocomBB) reader interface") print("Using Calypso-based (OsmocomBB) reader interface")
from pySim.transport.calypso import CalypsoSimLink from pySim.transport.calypso import CalypsoSimLink
sl = CalypsoSimLink(sock_path=opts.osmocon_sock, **kwargs) sl = CalypsoSimLink(sock_path=opts.osmocon_sock, **kwargs)
elif opts.modem_dev is not None: elif opts.modem_dev is not None:
print("Using modem for Generic SIM Access (3GPP TS 27.007)") print("Using modem for Generic SIM Access (3GPP TS 27.007)")
from pySim.transport.modem_atcmd import ModemATCommandLink from pySim.transport.modem_atcmd import ModemATCommandLink
sl = ModemATCommandLink(device=opts.modem_dev, baudrate=opts.modem_baud, **kwargs) sl = ModemATCommandLink(
else: # Serial reader is default device=opts.modem_dev, baudrate=opts.modem_baud, **kwargs)
print("Using serial reader interface") else: # Serial reader is default
from pySim.transport.serial import SerialSimLink print("Using serial reader interface")
sl = SerialSimLink(device=opts.device, baudrate=opts.baudrate, **kwargs) from pySim.transport.serial import SerialSimLink
return sl sl = SerialSimLink(device=opts.device,
except Exception as e: baudrate=opts.baudrate, **kwargs)
if str(e): return sl
print("Card reader initialization failed with exception:\n" + str(e)) except Exception as e:
else: if str(e):
print("Card reader initialization failed with an exception of type:\n" + str(type(e))) print("Card reader initialization failed with exception:\n" + str(e))
return None 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.exceptions import *
from pySim.utils import h2b, b2h from pySim.utils import h2b, b2h
class L1CTLMessage(object): class L1CTLMessage(object):
# Every (encoded) L1CTL message has the following structure: # Every (encoded) L1CTL message has the following structure:
# - msg_length (2 bytes, net order) # - msg_length (2 bytes, net order)
# - l1ctl_hdr (packed structure) # - l1ctl_hdr (packed structure)
# - msg_type # - msg_type
# - flags # - flags
# - padding (2 spare bytes) # - padding (2 spare bytes)
# - ... payload ... # - ... payload ...
def __init__(self, msg_type, flags = 0x00): def __init__(self, msg_type, flags=0x00):
# Init L1CTL message header # Init L1CTL message header
self.data = struct.pack("BBxx", msg_type, flags) 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): class L1CTLMessageReset(L1CTLMessage):
# L1CTL message types # L1CTL message types
L1CTL_RESET_REQ = 0x0d L1CTL_RESET_REQ = 0x0d
L1CTL_RESET_IND = 0x07 L1CTL_RESET_IND = 0x07
L1CTL_RESET_CONF = 0x0e L1CTL_RESET_CONF = 0x0e
# Reset types # Reset types
L1CTL_RES_T_BOOT = 0x00 L1CTL_RES_T_BOOT = 0x00
L1CTL_RES_T_FULL = 0x01 L1CTL_RES_T_FULL = 0x01
L1CTL_RES_T_SCHED = 0x02 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): class L1CTLMessageSIM(L1CTLMessage):
# SIM related message types # SIM related message types
L1CTL_SIM_REQ = 0x16 L1CTL_SIM_REQ = 0x16
L1CTL_SIM_CONF = 0x17 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): 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): def __init__(self, sock_path: str = "/tmp/osmocom_l2", **kwargs):
super().__init__(**kwargs) super().__init__(**kwargs)
# Make sure that a given socket path exists # Make sure that a given socket path exists
if not os.path.exists(sock_path): if not os.path.exists(sock_path):
raise ReaderError("There is no such ('%s') UNIX socket" % 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 # Establish a client connection
self.sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) self.sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
self.sock.connect(sock_path) self.sock.connect(sock_path)
def __del__(self): def __del__(self):
self.sock.close() self.sock.close()
def wait_for_rsp(self, exp_len = 128): def wait_for_rsp(self, exp_len=128):
# Wait for incoming data (timeout is 3 seconds) # Wait for incoming data (timeout is 3 seconds)
s, _, _ = select.select([self.sock], [], [], 3.0) s, _, _ = select.select([self.sock], [], [], 3.0)
if not s: if not s:
raise ReaderError("Timeout waiting for card response") raise ReaderError("Timeout waiting for card response")
# Receive expected amount of bytes from osmocon # Receive expected amount of bytes from osmocon
rsp = self.sock.recv(exp_len) rsp = self.sock.recv(exp_len)
return rsp return rsp
def reset_card(self): def reset_card(self):
# Request FULL reset # Request FULL reset
req_msg = L1CTLMessageReset() req_msg = L1CTLMessageReset()
self.sock.send(req_msg.gen_msg()) self.sock.send(req_msg.gen_msg())
# Wait for confirmation # Wait for confirmation
rsp = self.wait_for_rsp() rsp = self.wait_for_rsp()
rsp_msg = struct.unpack_from("!HB", rsp) rsp_msg = struct.unpack_from("!HB", rsp)
if rsp_msg[1] != L1CTLMessageReset.L1CTL_RESET_CONF: if rsp_msg[1] != L1CTLMessageReset.L1CTL_RESET_CONF:
raise ReaderError("Failed to reset Calypso PHY") raise ReaderError("Failed to reset Calypso PHY")
def connect(self): def connect(self):
self.reset_card() self.reset_card()
def disconnect(self): def disconnect(self):
pass # Nothing to do really ... pass # Nothing to do really ...
def wait_for_card(self, timeout = None, newcardonly = False): def wait_for_card(self, timeout=None, newcardonly=False):
pass # Nothing to do really ... pass # Nothing to do really ...
def _send_apdu_raw(self, pdu): def _send_apdu_raw(self, pdu):
# Request FULL reset # Request FULL reset
req_msg = L1CTLMessageSIM(h2b(pdu)) req_msg = L1CTLMessageSIM(h2b(pdu))
self.sock.send(req_msg.gen_msg()) self.sock.send(req_msg.gen_msg())
# Read message length first # Read message length first
rsp = self.wait_for_rsp(struct.calcsize("!H")) rsp = self.wait_for_rsp(struct.calcsize("!H"))
msg_len = struct.unpack_from("!H", rsp)[0] msg_len = struct.unpack_from("!H", rsp)[0]
if msg_len < struct.calcsize("BBxx"): if msg_len < struct.calcsize("BBxx"):
raise ReaderError("Missing L1CTL header for L1CTL_SIM_CONF") raise ReaderError("Missing L1CTL header for L1CTL_SIM_CONF")
# Read the whole message then # Read the whole message then
rsp = self.sock.recv(msg_len) rsp = self.sock.recv(msg_len)
# Verify L1CTL header # Verify L1CTL header
hdr = struct.unpack_from("BBxx", rsp) hdr = struct.unpack_from("BBxx", rsp)
if hdr[0] != L1CTLMessageSIM.L1CTL_SIM_CONF: if hdr[0] != L1CTLMessageSIM.L1CTL_SIM_CONF:
raise ReaderError("Unexpected L1CTL message received") raise ReaderError("Unexpected L1CTL message received")
# Verify the payload length # Verify the payload length
offset = struct.calcsize("BBxx") offset = struct.calcsize("BBxx")
if len(rsp) <= offset: if len(rsp) <= offset:
raise ProtocolError("Empty response from SIM?!?") raise ProtocolError("Empty response from SIM?!?")
# Omit L1CTL header # Omit L1CTL header
rsp = rsp[offset:] rsp = rsp[offset:]
# Unpack data and SW # Unpack data and SW
data = rsp[:-2] data = rsp[:-2]
sw = 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 # HACK: if somebody needs to debug this thing
# log.root.setLevel(log.DEBUG) # log.root.setLevel(log.DEBUG)
class ModemATCommandLink(LinkBase): class ModemATCommandLink(LinkBase):
"""Transport Link for 3GPP TS 27.007 compliant modems.""" """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
# Check the AT interface def __init__(self, device: str = '/dev/ttyUSB0', baudrate: int = 115200, **kwargs):
self._check_echo() 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 # Check the AT interface
self.reset_card() self._check_echo()
def __del__(self): # Trigger initial reset
if hasattr(self, '_sl'): self.reset_card()
self._sl.close()
def send_at_cmd(self, cmd, timeout=0.2, patience=0.002): def __del__(self):
# Convert from string to bytes, if needed if hasattr(self, '_sl'):
bcmd = cmd if type(cmd) is bytes else cmd.encode() self._sl.close()
bcmd += b'\r'
# Clean input buffer from previous/unexpected data def send_at_cmd(self, cmd, timeout=0.2, patience=0.002):
self._sl.reset_input_buffer() # 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 # Clean input buffer from previous/unexpected data
log.debug('Sending AT command: %s', cmd) self._sl.reset_input_buffer()
try:
wlen = self._sl.write(bcmd)
assert(wlen == len(bcmd))
except:
raise ReaderError('Failed to send AT command: %s' % cmd)
rsp = b'' # Send command to the modem
its = 1 log.debug('Sending AT command: %s', cmd)
t_start = time.time() try:
while True: wlen = self._sl.write(bcmd)
rsp = rsp + self._sl.read(self._sl.in_waiting) assert(wlen == len(bcmd))
lines = rsp.split(b'\r\n') except:
if len(lines) >= 2: raise ReaderError('Failed to send AT command: %s' % cmd)
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 time.time() - t_start >= timeout: rsp = b''
log.info('Command finished with timeout >= %ss', timeout) its = 1
break t_start = time.time()
time.sleep(patience) while True:
its += 1 rsp = rsp + self._sl.read(self._sl.in_waiting)
log.debug('Command took %0.6fs (%d cycles a %fs)', time.time() - t_start, its, patience) 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: if time.time() - t_start >= timeout:
# Skip echo chars log.info('Command finished with timeout >= %ss', timeout)
rsp = rsp[wlen:] break
rsp = rsp.strip() time.sleep(patience)
rsp = rsp.split(b'\r\n\r\n') 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) if self._echo:
return rsp # Skip echo chars
rsp = rsp[wlen:]
rsp = rsp.strip()
rsp = rsp.split(b'\r\n\r\n')
def _check_echo(self): log.debug('Got response from modem: %s', rsp)
"""Verify the correct response to 'AT' command return rsp
and detect if inputs are echoed by the device
Although echo of inputs can be enabled/disabled via def _check_echo(self):
ATE1/ATE0, respectively, we rather detect the current """Verify the correct response to 'AT' command
configuration of the modem without any change. and detect if inputs are echoed by the device
"""
# Next command shall not strip the echo from the response
self._echo = False
result = self.send_at_cmd('AT')
# Verify the response Although echo of inputs can be enabled/disabled via
if len(result) > 0: ATE1/ATE0, respectively, we rather detect the current
if result[-1] == b'OK': configuration of the modem without any change.
self._echo = False """
return # Next command shall not strip the echo from the response
elif result[-1] == b'AT\r\r\nOK': self._echo = False
self._echo = True result = self.send_at_cmd('AT')
return
raise ReaderError('Interface \'%s\' does not respond to \'AT\' command' % self._device)
def reset_card(self): # Verify the response
# Reset the modem, just to be sure if len(result) > 0:
if self.send_at_cmd('ATZ') != [b'OK']: if result[-1] == b'OK':
raise ReaderError('Failed to reset the modem') 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 def reset_card(self):
if self.send_at_cmd('AT+CSIM=?') != [b'OK']: # Reset the modem, just to be sure
raise ReaderError('The modem does not seem to support SIM access') 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): log.info('Modem at \'%s\' is ready!' % self._device)
pass # Nothing to do really ...
def disconnect(self): def connect(self):
pass # Nothing to do really ... pass # Nothing to do really ...
def wait_for_card(self, timeout=None, newcardonly=False): def disconnect(self):
pass # Nothing to do really ... pass # Nothing to do really ...
def _send_apdu_raw(self, pdu): def wait_for_card(self, timeout=None, newcardonly=False):
# Make sure pdu has upper case hex digits [A-F] pass # Nothing to do really ...
pdu = pdu.upper()
# Prepare the command as described in 8.17 def _send_apdu_raw(self, pdu):
cmd = 'AT+CSIM=%d,\"%s\"' % (len(pdu), pdu) # Make sure pdu has upper case hex digits [A-F]
log.debug('Sending command: %s', cmd) pdu = pdu.upper()
# Send AT+CSIM command to the modem # Prepare the command as described in 8.17
# TODO: also handle +CME ERROR: <err> cmd = 'AT+CSIM=%d,\"%s\"' % (len(pdu), pdu)
rsp = self.send_at_cmd(cmd) log.debug('Sending command: %s', 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'
# Make sure that the response has format: b'+CSIM: %d,\"%s\"' # Send AT+CSIM command to the modem
try: # TODO: also handle +CME ERROR: <err>
result = re.match(b'\+CSIM: (\d+),\"([0-9A-F]+)\"', rsp) rsp = self.send_at_cmd(cmd)
(rsp_pdu_len, rsp_pdu) = result.groups() if len(rsp) != 2 or rsp[-1] != b'OK':
except: raise ReaderError('APDU transfer failed: %s' % str(rsp))
raise ReaderError('Failed to parse response from modem: %s' % rsp) rsp = rsp[0] # Get rid of b'OK'
# TODO: make sure we have at least SW # Make sure that the response has format: b'+CSIM: %d,\"%s\"'
data = rsp_pdu[:-4].decode().lower() try:
sw = rsp_pdu[-4:].decode().lower() result = re.match(b'\+CSIM: (\d+),\"([0-9A-F]+)\"', rsp)
log.debug('Command response: %s, %s', data, sw) (rsp_pdu_len, rsp_pdu) = result.groups()
return data, sw 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): class PcscSimLink(LinkBase):
""" pySim: PCSC reader transport link.""" """ pySim: PCSC reader transport link."""
def __init__(self, reader_number:int=0, **kwargs): def __init__(self, reader_number: int = 0, **kwargs):
super().__init__(**kwargs) super().__init__(**kwargs)
r = readers() r = readers()
if reader_number >= len(r): if reader_number >= len(r):
raise ReaderError raise ReaderError
self._reader = r[reader_number] self._reader = r[reader_number]
self._con = self._reader.createConnection() self._con = self._reader.createConnection()
def __del__(self): def __del__(self):
try: try:
# FIXME: this causes multiple warnings in Python 3.5.3 # FIXME: this causes multiple warnings in Python 3.5.3
self._con.disconnect() self._con.disconnect()
except: except:
pass pass
return return
def wait_for_card(self, timeout:int=None, newcardonly:bool=False): def wait_for_card(self, timeout: int = None, newcardonly: bool = False):
cr = CardRequest(readers=[self._reader], timeout=timeout, newcardonly=newcardonly) cr = CardRequest(readers=[self._reader],
try: timeout=timeout, newcardonly=newcardonly)
cr.waitforcard() try:
except CardRequestTimeoutException: cr.waitforcard()
raise NoCardError() except CardRequestTimeoutException:
self.connect() raise NoCardError()
self.connect()
def connect(self): def connect(self):
try: try:
# To avoid leakage of resources, make sure the reader # To avoid leakage of resources, make sure the reader
# is disconnected # is disconnected
self.disconnect() self.disconnect()
# Explicitly select T=0 communication protocol # Explicitly select T=0 communication protocol
self._con.connect(CardConnection.T0_protocol) self._con.connect(CardConnection.T0_protocol)
except CardConnectionException: except CardConnectionException:
raise ProtocolError() raise ProtocolError()
except NoCardException: except NoCardException:
raise NoCardError() raise NoCardError()
def get_atr(self): def get_atr(self):
return self._con.getATR() return self._con.getATR()
def disconnect(self): def disconnect(self):
self._con.disconnect() self._con.disconnect()
def reset_card(self): def reset_card(self):
self.disconnect() self.disconnect()
self.connect() self.connect()
return 1 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 value
return i2h(data), i2h(sw) return i2h(data), i2h(sw)

View File

@ -26,209 +26,211 @@ from pySim.utils import h2b, b2h
class SerialSimLink(LinkBase): 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', def __init__(self, device: str = '/dev/ttyUSB0', baudrate: int = 9600, rst: str = '-rts',
debug:bool=False, **kwargs): debug: bool = False, **kwargs):
super().__init__(**kwargs) super().__init__(**kwargs)
if not os.path.exists(device): if not os.path.exists(device):
raise ValueError("device file %s does not exist -- abort" % device) raise ValueError("device file %s does not exist -- abort" % device)
self._sl = serial.Serial( self._sl = serial.Serial(
port = device, port=device,
parity = serial.PARITY_EVEN, parity=serial.PARITY_EVEN,
bytesize = serial.EIGHTBITS, bytesize=serial.EIGHTBITS,
stopbits = serial.STOPBITS_TWO, stopbits=serial.STOPBITS_TWO,
timeout = 1, timeout=1,
xonxoff = 0, xonxoff=0,
rtscts = 0, rtscts=0,
baudrate = baudrate, baudrate=baudrate,
) )
self._rst_pin = rst self._rst_pin = rst
self._debug = debug self._debug = debug
self._atr = None self._atr = None
def __del__(self): def __del__(self):
if (hasattr(self, "_sl")): if (hasattr(self, "_sl")):
self._sl.close() self._sl.close()
def wait_for_card(self, timeout=None, newcardonly=False): def wait_for_card(self, timeout=None, newcardonly=False):
# Direct try # Direct try
existing = False existing = False
try: try:
self.reset_card() self.reset_card()
if not newcardonly: if not newcardonly:
return return
else: else:
existing = True existing = True
except NoCardError: except NoCardError:
pass pass
# Poll ... # Poll ...
mt = time.time() + timeout if timeout is not None else None mt = time.time() + timeout if timeout is not None else None
pe = 0 pe = 0
while (mt is None) or (time.time() < mt): while (mt is None) or (time.time() < mt):
try: try:
time.sleep(0.5) time.sleep(0.5)
self.reset_card() self.reset_card()
if not existing: if not existing:
return return
except NoCardError: except NoCardError:
existing = False existing = False
except ProtocolError: except ProtocolError:
if existing: if existing:
existing = False existing = False
else: else:
# Tolerate a couple of protocol error ... can happen if # Tolerate a couple of protocol error ... can happen if
# we try when the card is 'half' inserted # we try when the card is 'half' inserted
pe += 1 pe += 1
if (pe > 2): if (pe > 2):
raise raise
# Timed out ... # Timed out ...
raise NoCardError() raise NoCardError()
def connect(self): def connect(self):
self.reset_card() self.reset_card()
def get_atr(self): def get_atr(self):
return self._atr return self._atr
def disconnect(self): def disconnect(self):
pass # Nothing to do really ... pass # Nothing to do really ...
def reset_card(self): def reset_card(self):
rv = self._reset_card() rv = self._reset_card()
if rv == 0: if rv == 0:
raise NoCardError() raise NoCardError()
elif rv < 0: elif rv < 0:
raise ProtocolError() raise ProtocolError()
def _reset_card(self): def _reset_card(self):
self._atr = None self._atr = None
rst_meth_map = { rst_meth_map = {
'rts': self._sl.setRTS, 'rts': self._sl.setRTS,
'dtr': self._sl.setDTR, 'dtr': self._sl.setDTR,
} }
rst_val_map = { '+':0, '-':1 } rst_val_map = {'+': 0, '-': 1}
try: try:
rst_meth = rst_meth_map[self._rst_pin[1:]] rst_meth = rst_meth_map[self._rst_pin[1:]]
rst_val = rst_val_map[self._rst_pin[0]] rst_val = rst_val_map[self._rst_pin[0]]
except: except:
raise ValueError('Invalid reset pin %s' % self._rst_pin) raise ValueError('Invalid reset pin %s' % self._rst_pin)
rst_meth(rst_val) rst_meth(rst_val)
time.sleep(0.1) # 100 ms time.sleep(0.1) # 100 ms
self._sl.flushInput() self._sl.flushInput()
rst_meth(rst_val ^ 1) rst_meth(rst_val ^ 1)
b = self._rx_byte() b = self._rx_byte()
if not b: if not b:
return 0 return 0
if ord(b) != 0x3b: if ord(b) != 0x3b:
return -1 return -1
self._dbg_print("TS: 0x%x Direct convention" % ord(b)) self._dbg_print("TS: 0x%x Direct convention" % ord(b))
while ord(b) == 0x3b: while ord(b) == 0x3b:
b = self._rx_byte() b = self._rx_byte()
if not b: if not b:
return -1 return -1
t0 = ord(b) t0 = ord(b)
self._dbg_print("T0: 0x%x" % t0) self._dbg_print("T0: 0x%x" % t0)
self._atr = [0x3b, ord(b)] self._atr = [0x3b, ord(b)]
for i in range(4): for i in range(4):
if t0 & (0x10 << i): if t0 & (0x10 << i):
b = self._rx_byte() b = self._rx_byte()
self._atr.append(ord(b)) self._atr.append(ord(b))
self._dbg_print("T%si = %x" % (chr(ord('A')+i), ord(b))) self._dbg_print("T%si = %x" % (chr(ord('A')+i), ord(b)))
for i in range(0, t0 & 0xf): for i in range(0, t0 & 0xf):
b = self._rx_byte() b = self._rx_byte()
self._atr.append(ord(b)) self._atr.append(ord(b))
self._dbg_print("Historical = %x" % ord(b)) self._dbg_print("Historical = %x" % ord(b))
while True: while True:
x = self._rx_byte() x = self._rx_byte()
if not x: if not x:
break break
self._atr.append(ord(x)) self._atr.append(ord(x))
self._dbg_print("Extra: %x" % ord(x)) self._dbg_print("Extra: %x" % ord(x))
return 1 return 1
def _dbg_print(self, s): def _dbg_print(self, s):
if self._debug: if self._debug:
print(s) print(s)
def _tx_byte(self, b): def _tx_byte(self, b):
self._sl.write(b) self._sl.write(b)
r = self._sl.read() r = self._sl.read()
if r != b: # TX and RX are tied, so we must clear the echo 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)')) raise ProtocolError("Bad echo value. Expected %02x, got %s)" % (
ord(b), '%02x' % ord(r) if r else '(nil)'))
def _tx_string(self, s): def _tx_string(self, s):
"""This is only safe if it's guaranteed the card won't send any data """This is only safe if it's guaranteed the card won't send any data
during the time of tx of the string !!!""" during the time of tx of the string !!!"""
self._sl.write(s) self._sl.write(s)
r = self._sl.read(len(s)) r = self._sl.read(len(s))
if r != s: # TX and RX are tied, so we must clear the echo 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))) raise ProtocolError(
"Bad echo value (Expected: %s, got %s)" % (b2h(s), b2h(r)))
def _rx_byte(self): def _rx_byte(self):
return self._sl.read() return self._sl.read()
def _send_apdu_raw(self, pdu): def _send_apdu_raw(self, pdu):
pdu = h2b(pdu) pdu = h2b(pdu)
data_len = pdu[4] # P3 data_len = pdu[4] # P3
# Send first CLASS,INS,P1,P2,P3 # Send first CLASS,INS,P1,P2,P3
self._tx_string(pdu[0:5]) self._tx_string(pdu[0:5])
# Wait ack which can be # Wait ack which can be
# - INS: Command acked -> go ahead # - INS: Command acked -> go ahead
# - 0x60: NULL, just wait some more # - 0x60: NULL, just wait some more
# - SW1: The card can apparently proceed ... # - SW1: The card can apparently proceed ...
while True: while True:
b = self._rx_byte() b = self._rx_byte()
if ord(b) == pdu[1]: if ord(b) == pdu[1]:
break break
elif b != '\x60': elif b != '\x60':
# Ok, it 'could' be SW1 # Ok, it 'could' be SW1
sw1 = b sw1 = b
sw2 = self._rx_byte() sw2 = self._rx_byte()
nil = self._rx_byte() nil = self._rx_byte()
if (sw2 and not nil): if (sw2 and not nil):
return '', b2h(sw1+sw2) return '', b2h(sw1+sw2)
raise ProtocolError() raise ProtocolError()
# Send data (if any) # Send data (if any)
if len(pdu) > 5: if len(pdu) > 5:
self._tx_string(pdu[5:]) self._tx_string(pdu[5:])
# Receive data (including SW !) # Receive data (including SW !)
# length = [P3 - tx_data (=len(pdu)-len(hdr)) + 2 (SW1//2) ] # length = [P3 - tx_data (=len(pdu)-len(hdr)) + 2 (SW1//2) ]
to_recv = data_len - len(pdu) + 5 + 2 to_recv = data_len - len(pdu) + 5 + 2
data = bytes(0) data = bytes(0)
while (len(data) < to_recv): while (len(data) < to_recv):
b = self._rx_byte() b = self._rx_byte()
if (to_recv == 2) and (b == '\x60'): # Ignore NIL if we have no RX data (hack ?) if (to_recv == 2) and (b == '\x60'): # Ignore NIL if we have no RX data (hack ?)
continue continue
if not b: if not b:
break break
data += b data += b
# Split datafield from SW # Split datafield from SW
if len(data) < 2: if len(data) < 2:
return None, None return None, None
sw = data[-2:] sw = data[-2:]
data = data[0:-2] data = data[0:-2]
# Return value # Return value
return b2h(data), b2h(sw) 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 EF', 0xE8, ['0X', '4X']),
CardCommand('TERMINATE CARD USAGE', 0xFE, ['0X', '4X']), CardCommand('TERMINATE CARD USAGE', 0xFE, ['0X', '4X']),
CardCommand('RESIZE FILE', 0xD4, ['8X', 'CX']), CardCommand('RESIZE FILE', 0xD4, ['8X', 'CX']),
]) ])
FCP_TLV_MAP = { FCP_TLV_MAP = {
@ -92,7 +92,7 @@ FCP_TLV_MAP = {
'80': 'file_size', '80': 'file_size',
'81': 'total_file_size', '81': 'total_file_size',
'88': 'short_file_id', '88': 'short_file_id',
} }
# ETSI TS 102 221 11.1.1.4.6 # ETSI TS 102 221 11.1.1.4.6
FCP_Proprietary_TLV_MAP = { FCP_Proprietary_TLV_MAP = {
@ -107,9 +107,11 @@ FCP_Proprietary_TLV_MAP = {
'88': 'specific_uicc_env_cond', '88': 'specific_uicc_env_cond',
'89': 'p2p_cat_secured_apdu', '89': 'p2p_cat_secured_apdu',
# Additional private TLV objects (bits b7 and b8 of the first byte of the tag set to '1') # 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 # ETSI TS 102 221 11.1.1.4.3
def interpret_file_descriptor(in_hex): def interpret_file_descriptor(in_hex):
in_bin = h2b(in_hex) in_bin = h2b(in_hex)
out = {} out = {}
@ -123,7 +125,7 @@ def interpret_file_descriptor(in_hex):
1: 'transparent', 1: 'transparent',
2: 'linear_fixed', 2: 'linear_fixed',
6: 'cyclic', 6: 'cyclic',
0x39: 'ber_tlv', 0x39: 'ber_tlv',
} }
fdb = in_bin[0] fdb = in_bin[0]
ftype = (fdb >> 3) & 7 ftype = (fdb >> 3) & 7
@ -140,6 +142,8 @@ def interpret_file_descriptor(in_hex):
return out return out
# ETSI TS 102 221 11.1.1.4.9 # ETSI TS 102 221 11.1.1.4.9
def interpret_life_cycle_sts_int(in_hex): def interpret_life_cycle_sts_int(in_hex):
lcsi = int(in_hex, 16) lcsi = int(in_hex, 16)
if lcsi == 0x00: if lcsi == 0x00:
@ -157,35 +161,40 @@ def interpret_life_cycle_sts_int(in_hex):
else: else:
return in_hex return in_hex
# ETSI TS 102 221 11.1.1.4.10 # ETSI TS 102 221 11.1.1.4.10
FCP_Pin_Status_TLV_MAP = { FCP_Pin_Status_TLV_MAP = {
'90': 'ps_do', '90': 'ps_do',
'95': 'usage_qualifier', '95': 'usage_qualifier',
'83': 'key_reference', '83': 'key_reference',
} }
def interpret_ps_templ_do(in_hex): def interpret_ps_templ_do(in_hex):
# cannot use the 'TLV' parser due to repeating tags # cannot use the 'TLV' parser due to repeating tags
#psdo_tlv = TLV(FCP_Pin_Status_TLV_MAP) #psdo_tlv = TLV(FCP_Pin_Status_TLV_MAP)
#return psdo_tlv.parse(in_hex) # return psdo_tlv.parse(in_hex)
return in_hex return in_hex
# 'interpreter' functions for each tag # 'interpreter' functions for each tag
FCP_interpreter_map = { FCP_interpreter_map = {
'80': lambda x: int(x, 16), '80': lambda x: int(x, 16),
'82': interpret_file_descriptor, '82': interpret_file_descriptor,
'8A': interpret_life_cycle_sts_int, '8A': interpret_life_cycle_sts_int,
'C6': interpret_ps_templ_do, 'C6': interpret_ps_templ_do,
} }
FCP_prorietary_interpreter_map = { FCP_prorietary_interpreter_map = {
'83': lambda x: int(x, 16), '83': lambda x: int(x, 16),
} }
# pytlv unfortunately doesn't have a setting using which we can make it # 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 # accept unknown tags. It also doesn't raise a specific exception type but
# just the generic ValueError, so we cannot ignore those either. Instead, # just the generic ValueError, so we cannot ignore those either. Instead,
# we insert a dict entry for every possible proprietary tag permitted # we insert a dict entry for every possible proprietary tag permitted
def fixup_fcp_proprietary_tlv_map(tlv_map): def fixup_fcp_proprietary_tlv_map(tlv_map):
if 'D0' in tlv_map: if 'D0' in tlv_map:
return return
@ -193,7 +202,7 @@ def fixup_fcp_proprietary_tlv_map(tlv_map):
i_hex = i2h([i]).upper() i_hex = i2h([i]).upper()
tlv_map[i_hex] = 'proprietary_' + i_hex tlv_map[i_hex] = 'proprietary_' + i_hex
# Other non-standard TLV objects found on some cards # 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): def tlv_key_replace(inmap, indata):
@ -204,6 +213,7 @@ def tlv_key_replace(inmap, indata):
return key return key
return {newkey(inmap, d[0]): d[1] for d in indata.items()} return {newkey(inmap, d[0]): d[1] for d in indata.items()}
def tlv_val_interpret(inmap, indata): def tlv_val_interpret(inmap, indata):
def newval(inmap, key, val): def newval(inmap, key, val):
if key in inmap: 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 # ETSI TS 102 221 Section 9.2.7 + ISO7816-4 9.3.3/9.3.4
class _AM_DO_DF(DataObject): class _AM_DO_DF(DataObject):
def __init__(self): def __init__(self):
super().__init__('access_mode', 'Access Mode', tag=0x80) super().__init__('access_mode', 'Access Mode', tag=0x80)
def from_bytes(self, do:bytes): def from_bytes(self, do: bytes):
res = [] res = []
if len(do) != 1: if len(do) != 1:
raise ValueError("We only support single-byte AMF inside AM-DO") 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): class _AM_DO_EF(DataObject):
"""ISO7816-4 9.3.2 Table 18 + 9.3.3.1 Table 31""" """ISO7816-4 9.3.2 Table 18 + 9.3.3.1 Table 31"""
def __init__(self): def __init__(self):
super().__init__('access_mode', 'Access Mode', tag=0x80) super().__init__('access_mode', 'Access Mode', tag=0x80)
def from_bytes(self, do:bytes): def from_bytes(self, do: bytes):
res = [] res = []
if len(do) != 1: if len(do) != 1:
raise ValueError("We only support single-byte AMF inside AM-DO") raise ValueError("We only support single-byte AMF inside AM-DO")
@ -306,12 +318,14 @@ class _AM_DO_EF(DataObject):
val |= 0x01 val |= 0x01
return val.to_bytes(1, 'big') return val.to_bytes(1, 'big')
class _AM_DO_CHDR(DataObject): class _AM_DO_CHDR(DataObject):
"""Command Header Access Mode DO according to ISO 7816-4 Table 32.""" """Command Header Access Mode DO according to ISO 7816-4 Table 32."""
def __init__(self, tag): def __init__(self, tag):
super().__init__('command_header', 'Command Header Description', tag=tag) super().__init__('command_header', 'Command Header Description', tag=tag)
def from_bytes(self, do:bytes): def from_bytes(self, do: bytes):
res = {} res = {}
i = 0 i = 0
if self.tag & 0x08: if self.tag & 0x08:
@ -353,11 +367,12 @@ class _AM_DO_CHDR(DataObject):
res.append(self.decoded['P2']) res.append(self.decoded['P2'])
return res return res
AM_DO_CHDR = DataObjectChoice('am_do_chdr', members=[ 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(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(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(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(0x8d), _AM_DO_CHDR(0x8e), _AM_DO_CHDR(0x8f)])
AM_DO_DF = AM_DO_CHDR | _AM_DO_DF() AM_DO_DF = AM_DO_CHDR | _AM_DO_DF()
AM_DO_EF = AM_DO_CHDR | _AM_DO_EF() AM_DO_EF = AM_DO_CHDR | _AM_DO_EF()
@ -393,12 +408,15 @@ pin_names = bidict({
0x8c: 'ADM8', 0x8c: 'ADM8',
0x8d: 'ADM9', 0x8d: 'ADM9',
0x8e: 'ADM10', 0x8e: 'ADM10',
}) })
class CRT_DO(DataObject): class CRT_DO(DataObject):
"""Control Reference Template as per TS 102 221 9.5.1""" """Control Reference Template as per TS 102 221 9.5.1"""
def __init__(self): 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): def from_bytes(self, do: bytes):
"""Decode a Control Reference Template DO.""" """Decode a Control Reference Template DO."""
@ -407,7 +425,8 @@ class CRT_DO(DataObject):
if do[0] != 0x83 or do[1] != 0x01: if do[0] != 0x83 or do[1] != 0x01:
raise ValueError('Unsupported Key Ref Tag or Len in CRT DO %s', do) raise ValueError('Unsupported Key Ref Tag or Len in CRT DO %s', do)
if do[3:] != b'\x95\x01\x08': 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.encoded = do[0:6]
self.decoded = pin_names[do[2]] self.decoded = pin_names[do[2]]
return do[6:] return do[6:]
@ -417,11 +436,13 @@ class CRT_DO(DataObject):
return b'\x83\x01' + pin.to_bytes(1, 'big') + b'\x95\x01\x08' return b'\x83\x01' + pin.to_bytes(1, 'big') + b'\x95\x01\x08'
# ISO7816-4 9.3.3 Table 33 # ISO7816-4 9.3.3 Table 33
class SecCondByte_DO(DataObject): class SecCondByte_DO(DataObject):
def __init__(self, tag=0x9d): def __init__(self, tag=0x9d):
super().__init__('security_condition_byte', tag=tag) super().__init__('security_condition_byte', tag=tag)
def from_bytes(self, binary:bytes): def from_bytes(self, binary: bytes):
if len(binary) != 1: if len(binary) != 1:
raise ValueError raise ValueError
inb = binary[0] inb = binary[0]
@ -440,7 +461,7 @@ class SecCondByte_DO(DataObject):
res.append('external_auth') res.append('external_auth')
if inb & 0x10: if inb & 0x10:
res.append('user_auth') res.append('user_auth')
rd = {'mode': cond } rd = {'mode': cond}
if len(res): if len(res):
rd['conditions'] = res rd['conditions'] = res
self.decoded = rd self.decoded = rd
@ -470,39 +491,47 @@ class SecCondByte_DO(DataObject):
raise ValueError('Unknown condition %s' % c) raise ValueError('Unknown condition %s' % c)
return res.to_bytes(1, 'big') return res.to_bytes(1, 'big')
Always_DO = TL0_DataObject('always', 'Always', 0x90) Always_DO = TL0_DataObject('always', 'Always', 0x90)
Never_DO = TL0_DataObject('never', 'Never', 0x97) Never_DO = TL0_DataObject('never', 'Never', 0x97)
class Nested_DO(DataObject): class Nested_DO(DataObject):
"""A DO that nests another DO/Choice/Sequence""" """A DO that nests another DO/Choice/Sequence"""
def __init__(self, name, tag, choice): def __init__(self, name, tag, choice):
super().__init__(name, tag=tag) super().__init__(name, tag=tag)
self.children = choice self.children = choice
def from_bytes(self, binary:bytes) -> list:
def from_bytes(self, binary: bytes) -> list:
remainder = binary remainder = binary
self.decoded = [] self.decoded = []
while remainder: while remainder:
rc, remainder = self.children.decode(remainder) rc, remainder = self.children.decode(remainder)
self.decoded.append(rc) self.decoded.append(rc)
return self.decoded return self.decoded
def to_bytes(self) -> bytes: def to_bytes(self) -> bytes:
encoded = [self.children.encode(d) for d in self.decoded] encoded = [self.children.encode(d) for d in self.decoded]
return b''.join(encoded) return b''.join(encoded)
OR_Template = DataObjectChoice('or_template', 'OR-Template', OR_Template = DataObjectChoice('or_template', 'OR-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()])
OR_DO = Nested_DO('or', 0xa0, OR_Template) OR_DO = Nested_DO('or', 0xa0, OR_Template)
AND_Template = DataObjectChoice('and_template', 'AND-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) AND_DO = Nested_DO('and', 0xa7, AND_Template)
NOT_Template = DataObjectChoice('not_template', 'NOT-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) NOT_DO = Nested_DO('not', 0xaf, NOT_Template)
SC_DO = DataObjectChoice('security_condition', 'Security Condition', SC_DO = DataObjectChoice('security_condition', 'Security Condition',
members=[Always_DO, Never_DO, SecCondByte_DO(), SecCondByte_DO(0x9e), CRT_DO(), members=[Always_DO, Never_DO, SecCondByte_DO(), SecCondByte_DO(0x9e), CRT_DO(),
OR_DO, AND_DO, NOT_DO]) OR_DO, AND_DO, NOT_DO])
# TS 102 221 Section 13.1 # TS 102 221 Section 13.1
class EF_DIR(LinFixedEF): class EF_DIR(LinFixedEF):
class ApplicationLabel(BER_TLV_IE, tag=0x50): class ApplicationLabel(BER_TLV_IE, tag=0x50):
# TODO: UCS-2 coding option as per Annex A of TS 102 221 # TODO: UCS-2 coding option as per Annex A of TS 102 221
@ -518,13 +547,15 @@ class EF_DIR(LinFixedEF):
pass pass
def __init__(self, fid='2f00', sfid=0x1e, name='EF.DIR', desc='Application Directory'): 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 self._tlv = EF_DIR.ApplicationTemplate
# TS 102 221 Section 13.2 # TS 102 221 Section 13.2
class EF_ICCID(TransparentEF): class EF_ICCID(TransparentEF):
def __init__(self, fid='2fe2', sfid=0x02, name='EF.ICCID', desc='ICC Identification'): 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): def _decode_hex(self, raw_hex):
return {'iccid': dec_iccid(raw_hex)} return {'iccid': dec_iccid(raw_hex)}
@ -533,14 +564,19 @@ class EF_ICCID(TransparentEF):
return enc_iccid(abstract['iccid']) return enc_iccid(abstract['iccid'])
# TS 102 221 Section 13.3 # TS 102 221 Section 13.3
class EF_PL(TransRecEF): class EF_PL(TransRecEF):
def __init__(self, fid='2f05', sfid=0x05, name='EF.PL', desc='Preferred Languages'): 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): def _decode_record_bin(self, bin_data):
if bin_data == b'\xff\xff': if bin_data == b'\xff\xff':
return None return None
else: else:
return bin_data.decode('ascii') return bin_data.decode('ascii')
def _encode_record_bin(self, in_json): def _encode_record_bin(self, in_json):
if in_json is None: if in_json is None:
return b'\xff\xff' return b'\xff\xff'
@ -556,7 +592,7 @@ class EF_ARR(LinFixedEF):
self.shell_commands += [self.AddlShellCommands()] self.shell_commands += [self.AddlShellCommands()]
@staticmethod @staticmethod
def flatten(inp:list): def flatten(inp: list):
"""Flatten the somewhat deep/complex/nested data returned from decoder.""" """Flatten the somewhat deep/complex/nested data returned from decoder."""
def sc_abbreviate(sc): def sc_abbreviate(sc):
if 'always' in sc: if 'always' in sc:
@ -584,7 +620,7 @@ class EF_ARR(LinFixedEF):
cla = None cla = None
cmd = ts_102_22x_cmdset.lookup(ins, cla) cmd = ts_102_22x_cmdset.lookup(ins, cla)
if cmd: if cmd:
name = cmd.name.lower().replace(' ','_') name = cmd.name.lower().replace(' ', '_')
by_mode[name] = sc_abbr by_mode[name] = sc_abbr
else: else:
raise ValueError raise ValueError
@ -594,7 +630,7 @@ class EF_ARR(LinFixedEF):
def _decode_record_bin(self, raw_bin_data): def _decode_record_bin(self, raw_bin_data):
# we can only guess if we should decode for EF or DF here :( # 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) dec = arr_seq.decode_multi(raw_bin_data)
# we cannot pass the result through flatten() here, as we don't have a related # 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 :( # 'un-flattening' decoder, and hence would be unable to encode :(
@ -628,15 +664,18 @@ class EF_ARR(LinFixedEF):
# TS 102 221 Section 13.6 # TS 102 221 Section 13.6
class EF_UMPC(TransparentEF): class EF_UMPC(TransparentEF):
def __init__(self, fid='2f08', sfid=0x08, name='EF.UMPC', desc='UICC Maximum Power Consumption'): 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}) 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) addl_info = FlagsEnum(Byte, req_inc_idle_current=1,
self._construct = Struct('max_current_mA'/Int8ub, 't_op_s'/Int8ub, 'addl_info'/addl_info) support_uicc_suspend=2)
self._construct = Struct(
'max_current_mA'/Int8ub, 't_op_s'/Int8ub, 'addl_info'/addl_info)
class CardProfileUICC(CardProfile): class CardProfileUICC(CardProfile):
ORDER = 1 ORDER = 1
def __init__(self, name = 'UICC'): def __init__(self, name='UICC'):
files = [ files = [
EF_DIR(), EF_DIR(),
EF_ICCID(), EF_ICCID(),
@ -646,78 +685,79 @@ class CardProfileUICC(CardProfile):
EF_UMPC(), EF_UMPC(),
] ]
sw = { sw = {
'Normal': { 'Normal': {
'9000': 'Normal ending of the command', '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', '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', '92xx': 'Normal ending of the command, with extra information concerning an ongoing data transfer session',
}, },
'Postponed processing': { 'Postponed processing': {
'9300': 'SIM Application Toolkit is busy. Command cannot be executed at present, further normal commands are allowed', '9300': 'SIM Application Toolkit is busy. Command cannot be executed at present, further normal commands are allowed',
}, },
'Warnings': { 'Warnings': {
'6200': 'No information given, state of non-volatile memory unchanged', '6200': 'No information given, state of non-volatile memory unchanged',
'6281': 'Part of returned data may be corrupted', '6281': 'Part of returned data may be corrupted',
'6282': 'End of file/record reached before reading Le bytes or unsuccessful search', '6282': 'End of file/record reached before reading Le bytes or unsuccessful search',
'6283': 'Selected file invalidated', '6283': 'Selected file invalidated',
'6284': 'Selected file in termination state', '6284': 'Selected file in termination state',
'62f1': 'More data available', '62f1': 'More data available',
'62f2': 'More data available and proactive command pending', '62f2': 'More data available and proactive command pending',
'62f3': 'Response data available', '62f3': 'Response data available',
'63f1': 'More data expected', '63f1': 'More data expected',
'63f2': 'More data expected and proactive command pending', '63f2': 'More data expected and proactive command pending',
'63cx': 'Command successful but after using an internal update retry routine X times', '63cx': 'Command successful but after using an internal update retry routine X times',
}, },
'Execution errors': { 'Execution errors': {
'6400': 'No information given, state of non-volatile memory unchanged', '6400': 'No information given, state of non-volatile memory unchanged',
'6500': 'No information given, state of non-volatile memory changed', '6500': 'No information given, state of non-volatile memory changed',
'6581': 'Memory problem', '6581': 'Memory problem',
}, },
'Checking errors': { 'Checking errors': {
'6700': 'Wrong length', '6700': 'Wrong length',
'67xx': 'The interpretation of this status word is command dependent', '67xx': 'The interpretation of this status word is command dependent',
'6b00': 'Wrong parameter(s) P1-P2', '6b00': 'Wrong parameter(s) P1-P2',
'6d00': 'Instruction code not supported or invalid', '6d00': 'Instruction code not supported or invalid',
'6e00': 'Class not supported', '6e00': 'Class not supported',
'6f00': 'Technical problem, no precise diagnosis', '6f00': 'Technical problem, no precise diagnosis',
'6fxx': 'The interpretation of this status word is command dependent', '6fxx': 'The interpretation of this status word is command dependent',
}, },
'Functions in CLA not supported': { 'Functions in CLA not supported': {
'6800': 'No information given', '6800': 'No information given',
'6881': 'Logical channel not supported', '6881': 'Logical channel not supported',
'6882': 'Secure messaging not supported', '6882': 'Secure messaging not supported',
}, },
'Command not allowed': { 'Command not allowed': {
'6900': 'No information given', '6900': 'No information given',
'6981': 'Command incompatible with file structure', '6981': 'Command incompatible with file structure',
'6982': 'Security status not satisfied', '6982': 'Security status not satisfied',
'6983': 'Authentication/PIN method blocked', '6983': 'Authentication/PIN method blocked',
'6984': 'Referenced data invalidated', '6984': 'Referenced data invalidated',
'6985': 'Conditions of use not satisfied', '6985': 'Conditions of use not satisfied',
'6986': 'Command not allowed (no EF selected)', '6986': 'Command not allowed (no EF selected)',
'6989': 'Command not allowed - secure channel - security not satisfied', '6989': 'Command not allowed - secure channel - security not satisfied',
}, },
'Wrong parameters': { 'Wrong parameters': {
'6a80': 'Incorrect parameters in the data field', '6a80': 'Incorrect parameters in the data field',
'6a81': 'Function not supported', '6a81': 'Function not supported',
'6a82': 'File not found', '6a82': 'File not found',
'6a83': 'Record not found', '6a83': 'Record not found',
'6a84': 'Not enough memory space', '6a84': 'Not enough memory space',
'6a86': 'Incorrect parameters P1 to P2', '6a86': 'Incorrect parameters P1 to P2',
'6a87': 'Lc inconsistent with P1 to P2', '6a87': 'Lc inconsistent with P1 to P2',
'6a88': 'Referenced data not found', '6a88': 'Referenced data not found',
}, },
'Application errors': { 'Application errors': {
'9850': 'INCREASE cannot be performed, max value reached', '9850': 'INCREASE cannot be performed, max value reached',
'9862': 'Authentication error, application specific', '9862': 'Authentication error, application specific',
'9863': 'Security session or association expired', '9863': 'Security session or association expired',
'9864': 'Minimum UICC suspension time is too long', '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 @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""" """ETSI TS 102 221 Section 11.1.1.3"""
fixup_fcp_proprietary_tlv_map(FCP_Proprietary_TLV_MAP) fixup_fcp_proprietary_tlv_map(FCP_Proprietary_TLV_MAP)
resp_hex = resp_hex.upper() resp_hex = resp_hex.upper()
@ -738,9 +778,10 @@ class CardProfileUICC(CardProfile):
return tlv_key_replace(FCP_TLV_MAP, r) return tlv_key_replace(FCP_TLV_MAP, r)
@staticmethod @staticmethod
def match_with_card(scc:SimCardCommands) -> bool: def match_with_card(scc: SimCardCommands) -> bool:
return match_uicc(scc) return match_uicc(scc)
class CardProfileUICCSIM(CardProfileUICC): class CardProfileUICCSIM(CardProfileUICC):
"""Same as above, but including 2G SIM support""" """Same as above, but including 2G SIM support"""
@ -754,5 +795,5 @@ class CardProfileUICCSIM(CardProfileUICC):
self.files_in_mf.append(DF_GSM()) self.files_in_mf.append(DF_GSM())
@staticmethod @staticmethod
def match_with_card(scc:SimCardCommands) -> bool: def match_with_card(scc: SimCardCommands) -> bool:
return match_uicc(scc) and match_sim(scc) 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 # Mapping between ISIM Service Number and its description
EF_IST_map = { EF_IST_map = {
1: 'P-CSCF address', 1: 'P-CSCF address',
2: 'Generic Bootstrapping Architecture (GBA)', 2: 'Generic Bootstrapping Architecture (GBA)',
3: 'HTTP Digest', 3: 'HTTP Digest',
4: 'GBA-based Local Key Establishment Mechanism', 4: 'GBA-based Local Key Establishment Mechanism',
5: 'Support of P-CSCF discovery for IMS Local Break Out', 5: 'Support of P-CSCF discovery for IMS Local Break Out',
6: 'Short Message Storage (SMS)', 6: 'Short Message Storage (SMS)',
7: 'Short Message Status Reports (SMSR)', 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]', 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', 9: 'Communication Control for IMS by ISIM',
10: 'Support of UICC access to IMS', 10: 'Support of UICC access to IMS',
11: 'URI support by UICC', 11: 'URI support by UICC',
12: 'Media Type support', 12: 'Media Type support',
13: 'IMS call disconnection cause', 13: 'IMS call disconnection cause',
14: 'URI support for MO SHORT MESSAGE CONTROL', 14: 'URI support for MO SHORT MESSAGE CONTROL',
15: 'MCPTT', 15: 'MCPTT',
16: 'URI support for SMS-PP DOWNLOAD as defined in 3GPP TS 31.111 [31]', 16: 'URI support for SMS-PP DOWNLOAD as defined in 3GPP TS 31.111 [31]',
17: 'From Preferred', 17: 'From Preferred',
18: 'IMS configuration data', 18: 'IMS configuration data',
19: 'XCAP Configuration Data', 19: 'XCAP Configuration Data',
20: 'WebRTC URI', 20: 'WebRTC URI',
21: 'MuD and MiD configuration data', 21: 'MuD and MiD configuration data',
} }
EF_ISIM_ADF_map = { EF_ISIM_ADF_map = {
'IST': '6F07', 'IST': '6F07',
'IMPI': '6F02', 'IMPI': '6F02',
'DOMAIN': '6F03', 'DOMAIN': '6F03',
'IMPU': '6F04', 'IMPU': '6F04',
'AD': '6FAD', 'AD': '6FAD',
'ARR': '6F06', 'ARR': '6F06',
'PCSCF': '6F09', 'PCSCF': '6F09',
'GBAP': '6FD5', 'GBAP': '6FD5',
'GBANL': '6FD7', 'GBANL': '6FD7',
'NAFKCA': '6FDD', 'NAFKCA': '6FDD',
'UICCIARI': '6FE7', 'UICCIARI': '6FE7',
'SMS': '6F3C', 'SMS': '6F3C',
'SMSS': '6F43', 'SMSS': '6F43',
'SMSR': '6F47', 'SMSR': '6F47',
'SMSP': '6F42', 'SMSP': '6F42',
'FromPreferred': '6FF7', 'FromPreferred': '6FF7',
'IMSConfigData': '6FF8', 'IMSConfigData': '6FF8',
'XCAPConfigData': '6FFC', 'XCAPConfigData': '6FFC',
'WebRTCURI': '6FFA' 'WebRTCURI': '6FFA'
} }
# TS 31.103 Section 4.2.2 # TS 31.103 Section 4.2.2
class EF_IMPI(TransparentEF): class EF_IMPI(TransparentEF):
class nai(BER_TLV_IE, tag=0x80): class nai(BER_TLV_IE, tag=0x80):
_construct = GreedyString("utf8") _construct = GreedyString("utf8")
def __init__(self, fid='6f02', sfid=0x02, name='EF.IMPI', desc='IMS private user identity'): 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) super().__init__(fid=fid, sfid=sfid, name=name, desc=desc)
self._tlv = EF_IMPI.nai self._tlv = EF_IMPI.nai
# TS 31.103 Section 4.2.3 # TS 31.103 Section 4.2.3
class EF_DOMAIN(TransparentEF): class EF_DOMAIN(TransparentEF):
class domain(BER_TLV_IE, tag=0x80): class domain(BER_TLV_IE, tag=0x80):
_construct = GreedyString("utf8") _construct = GreedyString("utf8")
def __init__(self, fid='6f05', sfid=0x05, name='EF.DOMAIN', desc='Home Network Domain Name'): 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) super().__init__(fid=fid, sfid=sfid, name=name, desc=desc)
self._tlv = EF_DOMAIN.domain self._tlv = EF_DOMAIN.domain
# TS 31.103 Section 4.2.4 # TS 31.103 Section 4.2.4
class EF_IMPU(LinFixedEF): class EF_IMPU(LinFixedEF):
class impu(BER_TLV_IE, tag=0x80): class impu(BER_TLV_IE, tag=0x80):
_construct = GreedyString("utf8") _construct = GreedyString("utf8")
def __init__(self, fid='6f04', sfid=0x04, name='EF.IMPU', desc='IMS public user identity'): 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) super().__init__(fid=fid, sfid=sfid, name=name, desc=desc)
self._tlv = EF_IMPU.impu self._tlv = EF_IMPU.impu
# TS 31.103 Section 4.2.8 # TS 31.103 Section 4.2.8
class EF_PCSCF(LinFixedEF): class EF_PCSCF(LinFixedEF):
def __init__(self, fid='6f09', sfid=None, name='EF.P-CSCF', desc='P-CSCF Address'): 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) super().__init__(fid=fid, sfid=sfid, name=name, desc=desc)
def _decode_record_hex(self, raw_hex): def _decode_record_hex(self, raw_hex):
addr, addr_type = dec_addr_tlv(raw_hex) addr, addr_type = dec_addr_tlv(raw_hex)
return {"addr": addr, "addr_type": addr_type} return {"addr": addr, "addr_type": addr_type}
def _encode_record_hex(self, json_in): def _encode_record_hex(self, json_in):
addr = json_in['addr'] addr = json_in['addr']
addr_type = json_in['addr_type'] addr_type = json_in['addr_type']
return enc_addr_tlv(addr, addr_type) return enc_addr_tlv(addr, addr_type)
# TS 31.103 Section 4.2.9 # TS 31.103 Section 4.2.9
class EF_GBABP(TransparentEF): class EF_GBABP(TransparentEF):
def __init__(self, fid='6fd5', sfid=None, name='EF.GBABP', desc='GBA Bootstrapping'): def __init__(self, fid='6fd5', sfid=None, name='EF.GBABP', desc='GBA Bootstrapping'):
super().__init__(fid=fid, sfid=sfid, name=name, desc=desc) super().__init__(fid=fid, sfid=sfid, name=name, desc=desc)
# TS 31.103 Section 4.2.10 # TS 31.103 Section 4.2.10
class EF_GBANL(LinFixedEF): class EF_GBANL(LinFixedEF):
def __init__(self, fid='6fd7', sfid=None, name='EF.GBANL', desc='GBA NAF List'): def __init__(self, fid='6fd7', sfid=None, name='EF.GBANL', desc='GBA NAF List'):
super().__init__(fid=fid, sfid=sfid, name=name, desc=desc) super().__init__(fid=fid, sfid=sfid, name=name, desc=desc)
# TS 31.103 Section 4.2.11 # TS 31.103 Section 4.2.11
class EF_NAFKCA(LinFixedEF): class EF_NAFKCA(LinFixedEF):
def __init__(self, fid='6fdd', sfid=None, name='EF.NAFKCA', desc='NAF Key Centre Address'): 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) super().__init__(fid=fid, sfid=sfid, name=name, desc=desc)
# TS 31.103 Section 4.2.16 # TS 31.103 Section 4.2.16
class EF_UICCIARI(LinFixedEF): class EF_UICCIARI(LinFixedEF):
class iari(BER_TLV_IE, tag=0x80): class iari(BER_TLV_IE, tag=0x80):
_construct = GreedyString("utf8") _construct = GreedyString("utf8")
def __init__(self, fid='6fe7', sfid=None, name='EF.UICCIARI', desc='UICC IARI'): def __init__(self, fid='6fe7', sfid=None, name='EF.UICCIARI', desc='UICC IARI'):
super().__init__(fid=fid, sfid=sfid, name=name, desc=desc) super().__init__(fid=fid, sfid=sfid, name=name, desc=desc)
self._tlv = EF_UICCIARI.iari self._tlv = EF_UICCIARI.iari
# TS 31.103 Section 4.2.18 # TS 31.103 Section 4.2.18
class EF_IMSConfigData(BerTlvEF): class EF_IMSConfigData(BerTlvEF):
def __init__(self, fid='6ff8', sfid=None, name='EF.IMSConfigData', desc='IMS Configuration Data'): def __init__(self, fid='6ff8', sfid=None, name='EF.IMSConfigData', desc='IMS Configuration Data'):
super().__init__(fid=fid, sfid=sfid, name=name, desc=desc) super().__init__(fid=fid, sfid=sfid, name=name, desc=desc)
# TS 31.103 Section 4.2.19 # TS 31.103 Section 4.2.19
class EF_XCAPConfigData(BerTlvEF): class EF_XCAPConfigData(BerTlvEF):
def __init__(self, fid='6ffc', sfid=None, name='EF.XCAPConfigData', desc='XCAP Configuration Data'): def __init__(self, fid='6ffc', sfid=None, name='EF.XCAPConfigData', desc='XCAP Configuration Data'):
super().__init__(fid=fid, sfid=sfid, name=name, desc=desc) super().__init__(fid=fid, sfid=sfid, name=name, desc=desc)
# TS 31.103 Section 4.2.20 # TS 31.103 Section 4.2.20
class EF_WebRTCURI(TransparentEF): class EF_WebRTCURI(TransparentEF):
class uri(BER_TLV_IE, tag=0x80): class uri(BER_TLV_IE, tag=0x80):
_construct = GreedyString("utf8") _construct = GreedyString("utf8")
def __init__(self, fid='6ffa', sfid=None, name='EF.WebRTCURI', desc='WebRTC URI'): def __init__(self, fid='6ffa', sfid=None, name='EF.WebRTCURI', desc='WebRTC URI'):
super().__init__(fid=fid, sfid=sfid, name=name, desc=desc) super().__init__(fid=fid, sfid=sfid, name=name, desc=desc)
self._tlv = EF_WebRTCURI.uri self._tlv = EF_WebRTCURI.uri
# TS 31.103 Section 4.2.21 # TS 31.103 Section 4.2.21
class EF_MuDMiDConfigData(BerTlvEF): class EF_MuDMiDConfigData(BerTlvEF):
def __init__(self, fid='6ffe', sfid=None, name='EF.MuDMiDConfigData', def __init__(self, fid='6ffe', sfid=None, name='EF.MuDMiDConfigData',
desc='MuD and MiD Configuration Data'): desc='MuD and MiD Configuration Data'):
@ -172,7 +203,8 @@ class ADF_ISIM(CardADF):
EF_IMPU(), EF_IMPU(),
EF_AD(), EF_AD(),
EF_ARR('6f06', 0x06), 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_PCSCF(),
EF_GBABP(), EF_GBABP(),
EF_GBANL(), EF_GBANL(),
@ -187,7 +219,7 @@ class ADF_ISIM(CardADF):
EF_XCAPConfigData(), EF_XCAPConfigData(),
EF_WebRTCURI(), EF_WebRTCURI(),
EF_MuDMiDConfigData(), EF_MuDMiDConfigData(),
] ]
self.add_files(files) self.add_files(files)
# add those commands to the general commands of a TransparentEF # add those commands to the general commands of a TransparentEF
self.shell_commands += [ADF_USIM.AddlShellCommands()] self.shell_commands += [ADF_USIM.AddlShellCommands()]
@ -195,6 +227,7 @@ class ADF_ISIM(CardADF):
def decode_select_response(self, data_hex): def decode_select_response(self, data_hex):
return pySim.ts_102_221.CardProfileUICC.decode_select_response(data_hex) return pySim.ts_102_221.CardProfileUICC.decode_select_response(data_hex)
# TS 31.103 Section 7.1 # TS 31.103 Section 7.1
sw_isim = { sw_isim = {
'Security management': { 'Security management': {
@ -203,6 +236,7 @@ sw_isim = {
} }
} }
class CardApplicationISIM(CardApplication): class CardApplicationISIM(CardApplication):
def __init__(self): 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