aboutsummaryrefslogtreecommitdiffstats
path: root/pySim/ts_102_221.py
blob: 88a36a1e62a6b04e92b9207dd15276dadd1bc799 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
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(0xc0, 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', desc='ETSI TS 102 221', files_in_mf=files, sw=sw)