blob: 5dee3e0bf9b515b87338d73de29940acef937296 [file] [log] [blame]
Harald Welte95ce6b12021-10-20 18:40:54 +02001# -*- coding: utf-8 -*-
2
3# without this, pylint will fail when inner classes are used
4# within the 'nested' kwarg of our TlvMeta metaclass on python 3.7 :(
5# pylint: disable=undefined-variable
6
7"""
8Support for the Secure Element Access Control, specifically the ARA-M inside an UICC.
9"""
10
11#
12# Copyright (C) 2021 Harald Welte <laforge@osmocom.org>
13#
14# This program is free software: you can redistribute it and/or modify
15# it under the terms of the GNU General Public License as published by
16# the Free Software Foundation, either version 2 of the License, or
17# (at your option) any later version.
18#
19# This program is distributed in the hope that it will be useful,
20# but WITHOUT ANY WARRANTY; without even the implied warranty of
21# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22# GNU General Public License for more details.
23#
24# You should have received a copy of the GNU General Public License
25# along with this program. If not, see <http://www.gnu.org/licenses/>.
26#
27
28
29from construct import *
30from construct import Optional as COptional
31from pySim.construct import *
32from pySim.filesystem import *
33from pySim.tlv import *
34
35# various BER-TLV encoded Data Objects (DOs)
36
37class AidRefDO(BER_TLV_IE, tag=0x4f):
38 # SEID v1.1 Table 6-3
39 _construct = HexAdapter(GreedyBytes)
40
41class AidRefEmptyDO(BER_TLV_IE, tag=0xc0):
42 # SEID v1.1 Table 6-3
43 pass
44
45class DevAppIdRefDO(BER_TLV_IE, tag=0xc1):
46 # SEID v1.1 Table 6-4
47 _construct = HexAdapter(GreedyBytes)
48
49class PkgRefDO(BER_TLV_IE, tag=0xca):
50 # Android UICC Carrier Privileges specific extension, see https://source.android.com/devices/tech/config/uicc
51 _construct = Struct('package_name_string'/GreedyString("ascii"))
52
53class RefDO(BER_TLV_IE, tag=0xe1, nested=[AidRefDO,AidRefEmptyDO,DevAppIdRefDO,PkgRefDO]):
54 # SEID v1.1 Table 6-5
55 pass
56
57class ApduArDO(BER_TLV_IE, tag=0xd0):
58 # SEID v1.1 Table 6-8
59 def _from_bytes(self, do:bytes):
60 if len(do) == 1:
61 if do[0] == 0x00:
62 self.decoded = {'generic_access_rule': 'never'}
63 return self.decoded
64 elif do[0] == 0x01:
65 self.decoded = {'generic_access_rule': 'always'}
66 return self.decoded
67 else:
68 return ValueError('Invalid 1-byte generic APDU access rule')
69 else:
70 if len(do) % 8:
71 return ValueError('Invalid non-modulo-8 length of APDU filter: %d' % len(do))
72 self.decoded['apdu_filter'] = []
73 offset = 0
74 while offset < len(do):
75 self.decoded['apdu_filter'] += {'header': b2h(do[offset:offset+4]),
76 'mask': b2h(do[offset+4:offset+8])}
77 self.decoded = res
78 return res
79 def _to_bytes(self):
80 if 'generic_access_rule' in self.decoded:
81 if self.decoded['generic_access_rule'] == 'never':
82 return b'\x00'
83 elif self.decoded['generic_access_rule'] == 'always':
84 return b'\x01'
85 else:
86 return ValueError('Invalid 1-byte generic APDU access rule')
87 else:
88 if not 'apdu_filter' in self.decoded:
89 return ValueError('Invalid APDU AR DO')
90 filters = self.decoded['apdu_filter']
91 res = b''
92 for f in filters:
93 if not 'header' in f or not 'mask' in f:
94 return ValueError('APDU filter must contain header and mask')
95 header_b = h2b(f['header'])
96 mask_b = h2b(f['mask'])
97 if len(header_b) != 4 or len(mask_b) != 4:
98 return ValueError('APDU filter header and mask must each be 4 bytes')
99 res += header_b + mask_b
100 return res
101
102class NfcArDO(BER_TLV_IE, tag=0xd1):
103 # SEID v1.1 Table 6-9
104 _construct = Struct('nfc_event_access_rule'/Enum(Int8ub, never=0, always=1))
105
106class PermArDO(BER_TLV_IE, tag=0xdb):
107 # Android UICC Carrier Privileges specific extension, see https://source.android.com/devices/tech/config/uicc
108 _construct = Struct('permissions'/HexAdapter(Bytes(8)))
109
110class ArDO(BER_TLV_IE, tag=0xe3, nested=[ApduArDO, NfcArDO, PermArDO]):
111 # SEID v1.1 Table 6-7
112 pass
113
114class RefArDO(BER_TLV_IE, tag=0xe2, nested=[RefDO, ArDO]):
115 # SEID v1.1 Table 6-6
116 pass
117
118class ResponseAllRefArDO(BER_TLV_IE, tag=0xff40, nested=[RefArDO]):
119 # SEID v1.1 Table 4-2
120 pass
121
122class ResponseArDO(BER_TLV_IE, tag=0xff50, nested=[ArDO]):
123 # SEID v1.1 Table 4-3
124 pass
125
126class ResponseRefreshTagDO(BER_TLV_IE, tag=0xdf20):
127 # SEID v1.1 Table 4-4
128 _construct = Struct('refresh_tag'/HexAdapter(Bytes(8)))
129
130class DeviceInterfaceVersionDO(BER_TLV_IE, tag=0xe6):
131 # SEID v1.1 Table 6-12
132 _construct = Struct('major'/Int8ub, 'minor'/Int8ub, 'patch'/Int8ub)
133
134class DeviceConfigDO(BER_TLV_IE, tag=0xe4, nested=[DeviceInterfaceVersionDO]):
135 # SEID v1.1 Table 6-10
136 pass
137
138class ResponseDeviceConfigDO(BER_TLV_IE, tag=0xff7f, nested=[DeviceConfigDO]):
139 # SEID v1.1 Table 5-14
140 pass
141
142class AramConfigDO(BER_TLV_IE, tag=0xe5, nested=[DeviceInterfaceVersionDO]):
143 # SEID v1.1 Table 6-11
144 pass
145
146class ResponseAramConfigDO(BER_TLV_IE, tag=0xdf21, nested=[AramConfigDO]):
147 # SEID v1.1 Table 4-5
148 pass
149
150class CommandStoreRefArDO(BER_TLV_IE, tag=0xf0, nested=[RefArDO]):
151 # SEID v1.1 Table 5-2
152 pass
153
154class CommandDelete(BER_TLV_IE, tag=0xf1, nested=[AidRefDO, AidRefEmptyDO, RefDO, RefArDO]):
155 # SEID v1.1 Table 5-4
156 pass
157
158class CommandUpdateRefreshTagDO(BER_TLV_IE, tag=0xf2):
159 # SEID V1.1 Table 5-6
160 pass
161
162class CommandRegisterClientAidsDO(BER_TLV_IE, tag=0xf7, nested=[AidRefDO, AidRefEmptyDO]):
163 # SEID v1.1 Table 5-7
164 pass
165
166class CommandGet(BER_TLV_IE, tag=0xf3, nested=[AidRefDO, AidRefEmptyDO]):
167 # SEID v1.1 Table 5-8
168 pass
169
170class CommandGetAll(BER_TLV_IE, tag=0xf4):
171 # SEID v1.1 Table 5-9
172 pass
173
174class CommandGetClientAidsDO(BER_TLV_IE, tag=0xf6):
175 # SEID v1.1 Table 5-10
176 pass
177
178class CommandGetNext(BER_TLV_IE, tag=0xf5):
179 # SEID v1.1 Table 5-11
180 pass
181
182class CommandGetDeviceConfigDO(BER_TLV_IE, tag=0xf8):
183 # SEID v1.1 Table 5-12
184 pass
185
186class ResponseAracAidDO(BER_TLV_IE, tag=0xff70, nested=[AidRefDO, AidRefEmptyDO]):
187 # SEID v1.1 Table 5-13
188 pass
189
190class BlockDO(BER_TLV_IE, tag=0xe7):
191 # SEID v1.1 Table 6-13
192 _construct = Struct('offset'/Int16ub, 'length'/Int8ub)
193
194
195# SEID v1.1 Table 4-1
196class GetCommandDoCollection(TLV_IE_Collection, nested=[RefDO, DeviceConfigDO]):
197 pass
198
199# SEID v1.1 Table 4-2
200class GetResponseDoCollection(TLV_IE_Collection, nested=[ResponseAllRefArDO, ResponseArDO,
201 ResponseRefreshTagDO, ResponseAramConfigDO]):
202 pass
203
204# SEID v1.1 Table 5-1
205class StoreCommandDoCollection(TLV_IE_Collection,
206 nested=[BlockDO, CommandStoreRefArDO, CommandDelete,
207 CommandUpdateRefreshTagDO, CommandRegisterClientAidsDO,
208 CommandGet, CommandGetAll, CommandGetClientAidsDO,
209 CommandGetNext, CommandGetDeviceConfigDO]):
210 pass
211
212
213# SEID v1.1 Section 5.1.2
214class StoreResponseDoCollection(TLV_IE_Collection,
215 nested=[ResponseAllRefArDO, ResponseAracAidDO, ResponseDeviceConfigDO]):
216 pass
217
218class ADF_ARAM(CardADF):
219 def __init__(self, aid='a00000015141434c00', name='ADF.ARA-M', fid=None, sfid=None,
220 desc='ARA-M Application'):
221 super().__init__(aid=aid, fid=fid, sfid=sfid, name=name, desc=desc)
222 self.shell_commands += [self.AddlShellCommands()]
223 files = []
224 self.add_files(files)
225
226 @staticmethod
227 def xceive_apdu_tlv(tp, hdr:Hexstr, cmd_do, resp_cls, exp_sw='9000'):
228 """Transceive an APDU with the card, transparently encoding the command data from TLV
229 and decoding the response data tlv."""
230 if cmd_do:
231 cmd_do_enc = cmd_do.to_ie()
232 cmd_do_len = len(cmd_do_enc)
233 if cmd_do_len > 255:
234 return ValueError('DO > 255 bytes not supported yet')
235 else:
236 cmd_do_enc = b''
237 cmd_do_len = 0
238 c_apdu = hdr + ('%02x' % cmd_do_len) + b2h(cmd_do_enc)
239 (data, sw) = tp.send_apdu_checksw(c_apdu, exp_sw)
240 if data:
241 if resp_cls:
242 resp_do = resp_cls()
243 resp_do.from_tlv(h2b(data))
244 return resp_do
245 else:
246 return data
247 else:
248 return None
249
250 @staticmethod
251 def store_data(tp, do) -> bytes:
252 """Build the Command APDU for STORE DATA."""
253 return ADF_ARAM.xceive_apdu_tlv(tp, '80e29000', do, StoreResponseDoCollection)
254
255 @staticmethod
256 def get_all(tp):
257 return ADF_ARAM.xceive_apdu_tlv(tp, '80caff40', None, GetResponseDoCollection)
258
259 @staticmethod
260 def get_config(tp, v_major=0, v_minor=0, v_patch=1):
261 cmd_do = DeviceConfigDO()
262 cmd_do.from_dict([{'DeviceInterfaceVersionDO': {'major': v_major, 'minor': v_minor, 'patch': v_patch }}])
263 return ADF_ARAM.xceive_apdu_tlv(tp, '80cadf21', cmd_do, ResponseAramConfigDO)
264
265 @with_default_category('Application-Specific Commands')
266 class AddlShellCommands(CommandSet):
267 def __init(self):
268 super().__init__()
269
270 def do_aram_get_all(self, opts):
271 """GET DATA [All] on the ARA-M Applet"""
272 res_do = ADF_ARAM.get_all(self._cmd.card._scc._tp)
273 if res_do:
274 self._cmd.poutput_json(res_do.to_dict())
275
276 def do_aram_get_config(self, opts):
277 """GET DATA [Config] on the ARA-M Applet"""
278 res_do = ADF_ARAM.get_config(self._cmd.card._scc._tp)
279 if res_do:
280 self._cmd.poutput_json(res_do.to_dict())
281
282 store_ref_ar_do_parse = argparse.ArgumentParser()
283 # REF-DO
284 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)')
285 aid_grp = store_ref_ar_do_parse.add_mutually_exclusive_group()
286 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)')
287 aid_grp.add_argument('--aid-empty', action='store_true', help='No specific SE application, applies to all applications')
288 store_ref_ar_do_parse.add_argument('--pkg-ref', help='Full Android Java package name (up to 127 chars ASCII)')
289 # AR-DO
290 apdu_grp = store_ref_ar_do_parse.add_mutually_exclusive_group()
291 apdu_grp.add_argument('--apdu-never', action='store_true', help='APDU access is not allowed')
292 apdu_grp.add_argument('--apdu-always', action='store_true', help='APDU access is allowed')
293 apdu_grp.add_argument('--apdu-filter', help='APDU filter: 4 byte CLA/INS/P1/P2 followed by 4 byte mask (8 hex bytes)')
294 nfc_grp = store_ref_ar_do_parse.add_mutually_exclusive_group()
295 nfc_grp.add_argument('--nfc-always', action='store_true', help='NFC event access is allowed')
296 nfc_grp.add_argument('--nfc-never', action='store_true', help='NFC event access is not allowed')
297 store_ref_ar_do_parse.add_argument('--android-permissions', help='Android UICC Carrier Privilege Permissions (8 hex bytes)')
298
299 @cmd2.with_argparser(store_ref_ar_do_parse)
300 def do_aram_store_ref_ar_do(self, opts):
301 """Perform STORE DATA [Command-Store-REF-AR-DO] to store a new access rule."""
302 # REF
303 ref_do_content = []
304 if opts.aid:
305 ref_do_content += [{'AidRefDO': opts.aid}]
306 elif opts.aid_empty:
307 ref_do_content += [{'AidRefEmptyDO': None}]
308 ref_do_content += [{'DevAppIdRefDO': opts.device_app_id}]
309 if opts.pkg_ref:
310 ref_do_content += [{'PkgRefDO': opts.pkg_ref}]
311 # AR
312 ar_do_content = []
313 if opts.apdu_never:
314 ar_do_content += [{'ApduArDO': {'generic_access_rule': 'never'}}]
315 elif opts.apdu_always:
316 ar_do_content += [{'ApduArDO': {'generic_access_rule': 'always'}}]
317 elif opts.apdu_filter:
318 # TODO: multiple filters
319 ar_do_content += [{'ApduArDO': {'apdu_filter': [opts.apdu_filter]}}]
320 if opts.nfc_always:
321 ar_do_content += [{'NfcArDO': {'nfc_event_access_rule': 'always'}}]
322 elif opts.nfc_never:
323 ar_do_content += [{'NfcArDO': {'nfc_event_access_rule': 'never'}}]
324 if opts.android_permissions:
325 ar_do_content += [{'PermArDO': {'permissions': opts.android_permissions}}]
326 d = [{'RefArDO': [{ 'RefDO': ref_do_content}, {'ArDO': ar_do_content }]}]
327 csrado = CommandStoreRefArDO()
328 csrado.from_dict(d)
329 res_do = ADF_ARAM.store_data(self._cmd.card._scc._tp, csrado)
330 if res_do:
331 self._cmd.poutput_json(res_do.to_dict())
332
333 def do_aram_delete_all(self, opts):
334 """Perform STORE DATA [Command-Delete[all]] to delete all access rules."""
335 deldo = CommandDelete()
336 res_do = ADF_ARAM.store_data(self._cmd.card._scc._tp, deldo)
337 if res_do:
338 self._cmd.poutput_json(res_do.to_dict())
339
340
341# SEAC v1.1 Section 4.1.2.2 + 5.1.2.2
342sw_aram = {
343 'ARA-M': {
344 '6381': 'Rule successfully stored but an access rule already exists',
345 '6382': 'Rule successfully stored bu contained at least one unknown (discarded) BER-TLV',
346 '6581': 'Memory Problem',
347 '6700': 'Wrong Length in Lc',
348 '6981': 'DO is not supported by the ARA-M/ARA-C',
349 '6982': 'Security status not satisfied',
350 '6984': 'Rules have been updated and must be read again / logical channels in use',
351 '6985': 'Conditions not satisfied',
352 '6a80': 'Incorrect values in the command data',
353 '6a84': 'Rules have been updated and must be read again',
354 '6a86': 'Incorrect P1 P2',
355 '6a88': 'Referenced data not found',
356 '6a89': 'Conflicting access rule already exists in the Secure Element',
357 '6d00': 'Invalid instruction',
358 '6e00': 'Invalid class',
359 }
360}
361
362class CardApplicationARAM(CardApplication):
363 def __init__(self):
364 super().__init__('ARA-M', adf=ADF_ARAM(), sw=sw_aram)