laforge has uploaded this change for review. ( https://gerrit.osmocom.org/c/pysim/+/27162 )
Change subject: ts_102_221: Proper parsing of FCP using pySim.tlv instead of pytlv ......................................................................
ts_102_221: Proper parsing of FCP using pySim.tlv instead of pytlv
pytlv is a nightmare of shortcomings, let's abandon it in favor of our own meanwhile-created pySim.tlv. This has the added benefit that unknown tags finally no longer raise exceptions.
Change-Id: Ic8e0e0ddf915949670d620630d4ceb02a9116471 Closes: OS#5414 --- M pySim/ts_102_221.py 1 file changed, 183 insertions(+), 164 deletions(-)
git pull ssh://gerrit.osmocom.org:29418/pysim refs/changes/62/27162/1
diff --git a/pySim/ts_102_221.py b/pySim/ts_102_221.py index da4eb93..b9a94b7 100644 --- a/pySim/ts_102_221.py +++ b/pySim/ts_102_221.py @@ -17,7 +17,6 @@ along with this program. If not, see http://www.gnu.org/licenses/. """
-from pytlv.TLV import * from construct import * from pySim.construct import * from pySim.utils import * @@ -78,131 +77,196 @@ CardCommand('RESIZE FILE', 0xD4, ['8X', 'CX']), ])
+# TS 102 221 Section 9.5.1 / Table 9.3 +pin_names = bidict({ + 0x01: 'PIN1', + 0x02: 'PIN2', + 0x03: 'PIN3', + 0x04: 'PIN4', + 0x05: 'PIN5', + 0x06: 'PIN6', + 0x07: 'PIN7', + 0x08: 'PIN8', + 0x0a: 'ADM1', + 0x0b: 'ADM2', + 0x0c: 'ADM3', + 0x0d: 'ADM4', + 0x0e: 'ADM5',
-FCP_TLV_MAP = { - '82': 'file_descriptor', - '83': 'file_identifier', - '84': 'df_name', - 'A5': 'proprietary_info', - '8A': 'life_cycle_status_int', - '8B': 'security_attrib_ref_expanded', - '8C': 'security_attrib_compact', - 'AB': 'security_attrib_espanded', - 'C6': 'pin_status_template_do', - '80': 'file_size', - '81': 'total_file_size', - '88': 'short_file_id', -} + 0x11: 'UNIVERSAL_PIN', + 0x81: '2PIN1', + 0x82: '2PIN2', + 0x83: '2PIN3', + 0x84: '2PIN4', + 0x85: '2PIN5', + 0x86: '2PIN6', + 0x87: '2PIN7', + 0x88: '2PIN8', + 0x8a: 'ADM6', + 0x8b: 'ADM7', + 0x8c: 'ADM8', + 0x8d: 'ADM9', + 0x8e: 'ADM10', +})
-# ETSI TS 102 221 11.1.1.4.6 -FCP_Proprietary_TLV_MAP = { - '80': 'uicc_characteristics', - '81': 'application_power_consumption', - '82': 'minimum_app_clock_freq', - '83': 'available_memory', - '84': 'file_details', - '85': 'reserved_file_size', - '86': 'maximum_file_size', - '87': 'suported_system_commands', - '88': 'specific_uicc_env_cond', - '89': 'p2p_cat_secured_apdu', - # Additional private TLV objects (bits b7 and b8 of the first byte of the tag set to '1') -} +# ETSI TS 102 221 11.1.1.4.2 +class FileSize(BER_TLV_IE, tag=0x80): + # FIXME: variable-length integer + _construct = BytesInteger(4) + +# ETSI TS 102 221 11.1.1.4.2 +class TotalFileSize(BER_TLV_IE, tag=0x81): + # FIXME: variable-length integer + _construct = BytesInteger(4)
# ETSI TS 102 221 11.1.1.4.3 +class FileDescriptor(BER_TLV_IE, tag=0x82): + def _from_bytes(self, in_bin: bytes): + out = {} + ft_dict = { + 0: 'working_ef', + 1: 'internal_ef', + 7: 'df' + } + fs_dict = { + 0: 'no_info_given', + 1: 'transparent', + 2: 'linear_fixed', + 6: 'cyclic', + 0x39: 'ber_tlv', + } + fdb = in_bin[0] + ftype = (fdb >> 3) & 7 + if fdb & 0xbf == 0x39: + fstruct = 0x39 + else: + fstruct = fdb & 7 + out['shareable'] = True if fdb & 0x40 else False + out['file_type'] = ft_dict[ftype] if ftype in ft_dict else ftype + out['structure'] = fs_dict[fstruct] if fstruct in fs_dict else fstruct + if len(in_bin) >= 5: + out['record_len'] = int.from_bytes(in_bin[2:4], 'big') + out['num_of_rec'] = int.from_bytes(in_bin[4:5], 'big') + self.decoded = out + return self.decoded
+# ETSI TS 102 221 11.1.1.4.4 +class FileIdentifier(BER_TLV_IE, tag=0x83): + _construct = GreedyBytes
-def interpret_file_descriptor(in_hex): - in_bin = h2b(in_hex) - out = {} - ft_dict = { - 0: 'working_ef', - 1: 'internal_ef', - 7: 'df' - } - fs_dict = { - 0: 'no_info_given', - 1: 'transparent', - 2: 'linear_fixed', - 6: 'cyclic', - 0x39: 'ber_tlv', - } - fdb = in_bin[0] - ftype = (fdb >> 3) & 7 - if fdb & 0xbf == 0x39: - fstruct = 0x39 - else: - fstruct = fdb & 7 - out['shareable'] = True if fdb & 0x40 else False - out['file_type'] = ft_dict[ftype] if ftype in ft_dict else ftype - out['structure'] = fs_dict[fstruct] if fstruct in fs_dict else fstruct - if len(in_bin) >= 5: - out['record_len'] = int.from_bytes(in_bin[2:4], 'big') - out['num_of_rec'] = int.from_bytes(in_bin[4:5], 'big') - return out +# ETSI TS 102 221 11.1.1.4.5 +class DfName(BER_TLV_IE, tag=0x84): + _construct = GreedyBytes + +# ETSI TS 102 221 11.1.1.4.6.1 +class UiccCharacteristics(BER_TLV_IE, tag=0x80): + _construct = GreedyBytes + +# ETSI TS 102 221 11.1.1.4.6.2 +class ApplicationPowerConsumption(BER_TLV_IE, tag=0x81): + _construct = Struct('voltage_class'/Int8ub, + 'power_consumption_ma'/Int8ub, + 'reference_freq_100k'/Int8ub) + +# ETSI TS 102 221 11.1.1.4.6.3 +class MinApplicationClockFrequency(BER_TLV_IE, tag=0x82): + _construct = Int8ub + +# ETSI TS 102 221 11.1.1.4.6.4 +class AvailableMemory(BER_TLV_IE, tag=0x83): + # FIXME: variable-length integer + _construct = BytesInteger(4) + +# ETSI TS 102 221 11.1.1.4.6.5 +class FileDetails(BER_TLV_IE, tag=0x84): + _construct = FlagsEnum(Byte, der_coding_only=1) + +# ETSI TS 102 221 11.1.1.4.6.6 +class ReservedFileSize(BER_TLV_IE, tag=0x85): + # FIXME: variable-length integer + _construct = BytesInteger(4) + +# ETSI TS 102 221 11.1.1.4.6.7 +class MaximumFileSize(BER_TLV_IE, tag=0x86): + # FIXME: variable-length integer + _construct = BytesInteger(4) + +# ETSI TS 102 221 11.1.1.4.6.8 +class SupportedFilesystemCommands(BER_TLV_IE, tag=0x87): + _construct = FlagsEnum(Byte, terminal_capability=1) + +# ETSI TS 102 221 11.1.1.4.6.9 +class SpecificUiccEnvironmentConditions(BER_TLV_IE, tag=0x88): + _construct = BitStruct('rfu'/BitsRFU(4), + 'high_humidity_supported'/Flag, + 'temperature_class'/Enum(BitsInteger(3), standard=0, class_A=1, class_B=2, class_C=3)) + +# ETSI TS 102 221 11.1.1.4.6.10 +class Platform2PlatformCatSecuredApdu(BER_TLV_IE, tag=0x89): + _construct = GreedyBytes + +# ETSI TS 102 221 11.1.1.4.6.0 +class ProprietaryInformation(BER_TLV_IE, tag=0xA5, + nested=[UiccCharacteristics, ApplicationPowerConsumption, + MinApplicationClockFrequency, AvailableMemory, + FileDetails, ReservedFileSize, MaximumFileSize, + SupportedFilesystemCommands, SpecificUiccEnvironmentConditions]): + pass + +# ETSI TS 102 221 11.1.1.4.7.1 +class SecurityAttribCompact(BER_TLV_IE, tag=0x8c): + _construct = GreedyBytes + +# ETSI TS 102 221 11.1.1.4.7.2 +class SecurityAttribExpanded(BER_TLV_IE, tag=0xab): + _construct = GreedyBytes + +# ETSI TS 102 221 11.1.1.4.7.3 +class SecurityAttribReferenced(BER_TLV_IE, tag=0x8b): + # TODO: longer format with SEID + _construct = Struct('ef_arr_file_id'/Bytes(2), 'ef_arr_record_nr'/Int8ub) + +# ETSI TS 102 221 11.1.1.4.8 +class ShortFileIdentifier(BER_TLV_IE, tag=0x88): + _construct = Byte
# ETSI TS 102 221 11.1.1.4.9 +class LifeCycleStatusInteger(BER_TLV_IE, tag=0x8A): + def _from_bytes(self, do: bytes): + lcsi = int.from_bytes(do, 'big') + if lcsi == 0x00: + ret = 'no_information' + elif lcsi == 0x01: + ret = 'creation' + elif lcsi == 0x03: + ret = 'initialization' + elif lcsi & 0x05 == 0x05: + ret = 'operational_activated' + elif lcsi & 0x05 == 0x04: + ret = 'operational_deactivated' + elif lcsi & 0xc0 == 0xc0: + ret = 'termination' + else: + ret = lcsi + self.decoded = ret + return self.decoded
+# ETSI TS 102 221 11.1.1.4.9 +class PS_DO(BER_TLV_IE, tag=0x90): + _construct = GreedyBytes +class UsageQualifier_DO(BER_TLV_IE, tag=0x95): + _construct = GreedyBytes +class KeyReference(BER_TLV_IE, tag=0x83): + _construct = Byte +class PinStatusTemplate_DO(BER_TLV_IE, tag=0xC6, nested=[PS_DO, UsageQualifier_DO, KeyReference]): + pass
-def interpret_life_cycle_sts_int(in_hex): - lcsi = int(in_hex, 16) - if lcsi == 0x00: - return 'no_information' - elif lcsi == 0x01: - return 'creation' - elif lcsi == 0x03: - return 'initialization' - elif lcsi & 0x05 == 0x05: - return 'operational_activated' - elif lcsi & 0x05 == 0x04: - return 'operational_deactivated' - elif lcsi & 0xc0 == 0xc0: - return 'termination' - else: - return in_hex - - -# ETSI TS 102 221 11.1.1.4.10 -FCP_Pin_Status_TLV_MAP = { - '90': 'ps_do', - '95': 'usage_qualifier', - '83': 'key_reference', -} - - -def interpret_ps_templ_do(in_hex): - # cannot use the 'TLV' parser due to repeating tags - #psdo_tlv = TLV(FCP_Pin_Status_TLV_MAP) - # return psdo_tlv.parse(in_hex) - return in_hex - - -# 'interpreter' functions for each tag -FCP_interpreter_map = { - '80': lambda x: int(x, 16), - '82': interpret_file_descriptor, - '8A': interpret_life_cycle_sts_int, - 'C6': interpret_ps_templ_do, -} - -FCP_prorietary_interpreter_map = { - '83': lambda x: int(x, 16), -} - -# pytlv unfortunately doesn't have a setting using which we can make it -# accept unknown tags. It also doesn't raise a specific exception type but -# just the generic ValueError, so we cannot ignore those either. Instead, -# we insert a dict entry for every possible proprietary tag permitted - - -def fixup_fcp_proprietary_tlv_map(tlv_map): - if 'D0' in tlv_map: - return - for i in range(0xc0, 0xff): - i_hex = i2h([i]).upper() - tlv_map[i_hex] = 'proprietary_' + i_hex - # Other non-standard TLV objects found on some cards - tlv_map['9B'] = 'target_ef' # for sysmoUSIM-SJS1 +class FcpTemplate(BER_TLV_IE, tag=0x62, nested=[FileSize, TotalFileSize, FileDescriptor, FileIdentifier, + DfName, ProprietaryInformation, SecurityAttribCompact, + SecurityAttribExpanded, SecurityAttribReferenced, + ShortFileIdentifier, LifeCycleStatusInteger, + PinStatusTemplate_DO]): + pass
def tlv_key_replace(inmap, indata): @@ -378,37 +442,6 @@ AM_DO_EF = AM_DO_CHDR | _AM_DO_EF()
-# TS 102 221 Section 9.5.1 / Table 9.3 -pin_names = bidict({ - 0x01: 'PIN1', - 0x02: 'PIN2', - 0x03: 'PIN3', - 0x04: 'PIN4', - 0x05: 'PIN5', - 0x06: 'PIN6', - 0x07: 'PIN7', - 0x08: 'PIN8', - 0x0a: 'ADM1', - 0x0b: 'ADM2', - 0x0c: 'ADM3', - 0x0d: 'ADM4', - 0x0e: 'ADM5', - - 0x11: 'UNIVERSAL_PIN', - 0x81: '2PIN1', - 0x82: '2PIN2', - 0x83: '2PIN3', - 0x84: '2PIN4', - 0x85: '2PIN5', - 0x86: '2PIN6', - 0x87: '2PIN7', - 0x88: '2PIN8', - 0x8a: 'ADM6', - 0x8b: 'ADM7', - 0x8c: 'ADM8', - 0x8d: 'ADM9', - 0x8e: 'ADM10', -})
class CRT_DO(DataObject): @@ -759,23 +792,9 @@ @staticmethod def decode_select_response(resp_hex: str) -> object: """ETSI TS 102 221 Section 11.1.1.3""" - fixup_fcp_proprietary_tlv_map(FCP_Proprietary_TLV_MAP) - resp_hex = resp_hex.upper() - # outer layer - fcp_base_tlv = TLV(['62']) - fcp_base = fcp_base_tlv.parse(resp_hex) - # actual FCP - fcp_tlv = TLV(FCP_TLV_MAP) - fcp = fcp_tlv.parse(fcp_base['62']) - # further decode the proprietary information - if 'A5' in fcp: - prop_tlv = TLV(FCP_Proprietary_TLV_MAP) - prop = prop_tlv.parse(fcp['A5']) - fcp['A5'] = tlv_val_interpret(FCP_prorietary_interpreter_map, prop) - fcp['A5'] = tlv_key_replace(FCP_Proprietary_TLV_MAP, fcp['A5']) - # finally make sure we get human-readable keys in the output dict - r = tlv_val_interpret(FCP_interpreter_map, fcp) - return tlv_key_replace(FCP_TLV_MAP, r) + t = FcpTemplate() + t.from_tlv(h2b(resp_hex)) + return t.to_dict()
@staticmethod def match_with_card(scc: SimCardCommands) -> bool: