aboutsummaryrefslogtreecommitdiffstats
path: root/pySim/ts_102_221.py
diff options
context:
space:
mode:
Diffstat (limited to 'pySim/ts_102_221.py')
-rw-r--r--pySim/ts_102_221.py297
1 files changed, 297 insertions, 0 deletions
diff --git a/pySim/ts_102_221.py b/pySim/ts_102_221.py
new file mode 100644
index 0000000..256a697
--- /dev/null
+++ b/pySim/ts_102_221.py
@@ -0,0 +1,297 @@
+# coding=utf-8
+"""Utilities / Functions related to ETSI TS 102 221, the core UICC spec.
+
+(C) 2021 by Harald Welte <laforge@osmocom.org>
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 2 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>.
+"""
+
+from pytlv.TLV import *
+from struct import pack, unpack
+from pySim.utils import *
+from pySim.filesystem import *
+
+
+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',
+ }
+
+# 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.3
+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',
+ }
+ fdb = in_bin[0]
+ ftype = (fdb >> 3) & 7
+ 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.9
+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(0xd0, 0xff):
+ i_hex = i2h([i]).upper()
+ tlv_map[i_hex] = 'proprietary_' + i_hex
+
+
+def tlv_key_replace(inmap, indata):
+ def newkey(inmap, key):
+ if key in inmap:
+ return inmap[key]
+ else:
+ return key
+ return {newkey(inmap, d[0]): d[1] for d in indata.items()}
+
+def tlv_val_interpret(inmap, indata):
+ def newval(inmap, key, val):
+ if key in inmap:
+ return inmap[key](val)
+ else:
+ return val
+ return {d[0]: newval(inmap, d[0], d[1]) for d in indata.items()}
+
+
+# ETSI TS 102 221 Section 11.1.1.3
+def decode_select_response(resp_hex):
+ 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 fcp['A5']:
+ 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)
+
+
+# TS 102 221 Section 13.1
+class EF_DIR(LinFixedEF):
+ 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})
+
+ def _decode_record_hex(self, raw_hex_data):
+ raw_hex_data = raw_hex_data.upper()
+ atempl_base_tlv = TLV(['61'])
+ atempl_base = atempl_base_tlv.parse(raw_hex_data)
+ atempl_TLV_MAP = {'4F': 'aid_value', 50:'label'}
+ atempl_tlv = TLV(atempl_TLV_MAP)
+ atempl = atempl_tlv.parse(atempl_base['61'])
+ # FIXME: "All other Dos are according to ISO/IEC 7816-4"
+ return tlv_key_replace(atempl_TLV_MAP, atempl)
+
+# TS 102 221 Section 13.2
+class EF_ICCID(TransparentEF):
+ def __init__(self, fid='2fe2', sfid=0x02, name='EF.ICCID', desc='ICC Identification'):
+ super().__init__(fid, sfid=sfid, name=name, desc=desc, size={10,10})
+
+ def _decode_hex(self, raw_hex):
+ return {'iccid': dec_iccid(raw_hex)}
+
+ def _encode_hex(self, abstract):
+ return enc_iccid(abstract['iccid'])
+
+# TS 102 221 Section 13.3
+class EF_PL(TransRecEF):
+ def __init__(self, fid='2f05', sfid=0x05, name='EF.PL', desc='Preferred Languages'):
+ super().__init__(fid, sfid=sfid, name=name, desc=desc, rec_len=2, size={2,None})
+
+# TS 102 221 Section 13.4
+class EF_ARR(LinFixedEF):
+ def __init__(self, fid='2f06', sfid=0x06, name='EF.ARR', desc='Access Rule Reference'):
+ super().__init__(fid, sfid=sfid, name=name, desc=desc)
+
+# TS 102 221 Section 13.6
+class EF_UMPC(TransparentEF):
+ def __init__(self, fid='2f08', sfid=0x08, name='EF.UMPC', desc='UICC Maximum Power Consumption'):
+ super().__init__(fid, sfid=sfid, name=name, desc=desc, size={5,5})
+
+
+
+class CardProfileUICC(CardProfile):
+ def __init__(self):
+ files = [
+ EF_DIR(),
+ EF_ICCID(),
+ EF_PL(),
+ EF_ARR(),
+ # FIXME: DF.CD
+ EF_UMPC(),
+ ]
+ sw = {
+ 'Normal': {
+ '9000': 'Normal ending of the command',
+ '91xx': 'Normal ending of the command, with extra information from the proactive UICC containing a command for the terminal',
+ '92xx': 'Normal ending of the command, with extra information concerning an ongoing data transfer session',
+ },
+ 'Postponed processing': {
+ '9300': 'SIM Application Toolkit is busy. Command cannot be executed at present, further normal commands are allowed',
+ },
+ 'Warnings': {
+ '6200': 'No information given, state of non-volatile memory unchanged',
+ '6281': 'Part of returned data may be corrupted',
+ '6282': 'End of file/record reached before reading Le bytes or unsuccessful search',
+ '6283': 'Selected file invalidated',
+ '6284': 'Selected file in termination state',
+ '62f1': 'More data available',
+ '62f2': 'More data available and proactive command pending',
+ '62f3': 'Response data available',
+ '63f1': 'More data expected',
+ '63f2': 'More data expected and proactive command pending',
+ '63cx': 'Command successful but after using an internal update retry routine X times',
+ },
+ 'Execution errors': {
+ '6400': 'No information given, state of non-volatile memory unchanged',
+ '6500': 'No information given, state of non-volatile memory changed',
+ '6581': 'Memory problem',
+ },
+ 'Checking errors': {
+ '6700': 'Wrong length',
+ '67xx': 'The interpretation of this status word is command dependent',
+ '6b00': 'Wrong parameter(s) P1-P2',
+ '6d00': 'Instruction code not supported or invalid',
+ '6e00': 'Class not supported',
+ '6f00': 'Technical problem, no precise diagnosis',
+ '6fxx': 'The interpretation of this status word is command dependent',
+ },
+ 'Functions in CLA not supported': {
+ '6800': 'No information given',
+ '6881': 'Logical channel not supported',
+ '6882': 'Secure messaging not supported',
+ },
+ 'Command not allowed': {
+ '6900': 'No information given',
+ '6981': 'Command incompatible with file structure',
+ '6982': 'Security status not satisfied',
+ '6983': 'Authentication/PIN method blocked',
+ '6984': 'Referenced data invalidated',
+ '6985': 'Conditions of use not satisfied',
+ '6986': 'Command not allowed (no EF selected)',
+ '6989': 'Command not allowed - secure channel - security not satisfied',
+ },
+ 'Wrong parameters': {
+ '6a80': 'Incorrect parameters in the data field',
+ '6a81': 'Function not supported',
+ '6a82': 'File not found',
+ '6a83': 'Record not found',
+ '6a84': 'Not enough memory space',
+ '6a86': 'Incorrect parameters P1 to P2',
+ '6a87': 'Lc inconsistent with P1 to P2',
+ '6a88': 'Referenced data not found',
+ },
+ 'Application errors': {
+ '9850': 'INCREASE cannot be performed, max value reached',
+ '9862': 'Authentication error, application specific',
+ '9863': 'Security session or association expired',
+ '9864': 'Minimum UICC suspension time is too long',
+ },
+ }
+
+ super().__init__('UICC', 'ETSI TS 102 221', files, sw)