# -*- coding: utf-8 -*- # without this, pylint will fail when inner classes are used # within the 'nested' kwarg of our TlvMeta metaclass on python 3.7 :( # pylint: disable=undefined-variable """ Support for the Secure Element Access Control, specifically the ARA-M inside an UICC. """ # # Copyright (C) 2021 Harald Welte # # 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 . # from construct import GreedyBytes, GreedyString, Struct, Enum, Int8ub, Int16ub from construct import Optional as COptional from pySim.construct import * from pySim.filesystem import * from pySim.tlv import * from pySim.utils import Hexstr import pySim.global_platform # various BER-TLV encoded Data Objects (DOs) class AidRefDO(BER_TLV_IE, tag=0x4f): # SEID v1.1 Table 6-3 _construct = HexAdapter(GreedyBytes) class AidRefEmptyDO(BER_TLV_IE, tag=0xc0): # SEID v1.1 Table 6-3 pass class DevAppIdRefDO(BER_TLV_IE, tag=0xc1): # SEID v1.1 Table 6-4 _construct = HexAdapter(GreedyBytes) class PkgRefDO(BER_TLV_IE, tag=0xca): # Android UICC Carrier Privileges specific extension, see https://source.android.com/devices/tech/config/uicc _construct = Struct('package_name_string'/GreedyString("ascii")) class RefDO(BER_TLV_IE, tag=0xe1, nested=[AidRefDO, AidRefEmptyDO, DevAppIdRefDO, PkgRefDO]): # SEID v1.1 Table 6-5 pass class ApduArDO(BER_TLV_IE, tag=0xd0): # SEID v1.1 Table 6-8 def _from_bytes(self, do: bytes): if len(do) == 1: if do[0] == 0x00: self.decoded = {'generic_access_rule': 'never'} return self.decoded if do[0] == 0x01: self.decoded = {'generic_access_rule': 'always'} return self.decoded return ValueError('Invalid 1-byte generic APDU access rule') else: if len(do) % 8: return ValueError('Invalid non-modulo-8 length of APDU filter: %d' % len(do)) self.decoded['apdu_filter'] = [] offset = 0 while offset < len(do): self.decoded['apdu_filter'] += {'header': b2h(do[offset:offset+4]), 'mask': b2h(do[offset+4:offset+8])} self.decoded = res return res def _to_bytes(self): if 'generic_access_rule' in self.decoded: if self.decoded['generic_access_rule'] == 'never': return b'\x00' if self.decoded['generic_access_rule'] == 'always': return b'\x01' return ValueError('Invalid 1-byte generic APDU access rule') else: if not 'apdu_filter' in self.decoded: return ValueError('Invalid APDU AR DO') filters = self.decoded['apdu_filter'] res = b'' for f in filters: if not 'header' in f or not 'mask' in f: return ValueError('APDU filter must contain header and mask') header_b = h2b(f['header']) mask_b = h2b(f['mask']) if len(header_b) != 4 or len(mask_b) != 4: return ValueError('APDU filter header and mask must each be 4 bytes') res += header_b + mask_b return res class NfcArDO(BER_TLV_IE, tag=0xd1): # SEID v1.1 Table 6-9 _construct = Struct('nfc_event_access_rule' / Enum(Int8ub, never=0, always=1)) class PermArDO(BER_TLV_IE, tag=0xdb): # Android UICC Carrier Privileges specific extension, see https://source.android.com/devices/tech/config/uicc # based on Table 6-8 of GlobalPlatform Device API Access Control v1.0 _construct = Struct('permissions'/HexAdapter(Bytes(8))) class ArDO(BER_TLV_IE, tag=0xe3, nested=[ApduArDO, NfcArDO, PermArDO]): # SEID v1.1 Table 6-7 pass class RefArDO(BER_TLV_IE, tag=0xe2, nested=[RefDO, ArDO]): # SEID v1.1 Table 6-6 pass class ResponseAllRefArDO(BER_TLV_IE, tag=0xff40, nested=[RefArDO]): # SEID v1.1 Table 4-2 pass class ResponseArDO(BER_TLV_IE, tag=0xff50, nested=[ArDO]): # SEID v1.1 Table 4-3 pass class ResponseRefreshTagDO(BER_TLV_IE, tag=0xdf20): # SEID v1.1 Table 4-4 _construct = Struct('refresh_tag'/HexAdapter(Bytes(8))) class DeviceInterfaceVersionDO(BER_TLV_IE, tag=0xe6): # SEID v1.1 Table 6-12 _construct = Struct('major'/Int8ub, 'minor'/Int8ub, 'patch'/Int8ub) class DeviceConfigDO(BER_TLV_IE, tag=0xe4, nested=[DeviceInterfaceVersionDO]): # SEID v1.1 Table 6-10 pass class ResponseDeviceConfigDO(BER_TLV_IE, tag=0xff7f, nested=[DeviceConfigDO]): # SEID v1.1 Table 5-14 pass class AramConfigDO(BER_TLV_IE, tag=0xe5, nested=[DeviceInterfaceVersionDO]): # SEID v1.1 Table 6-11 pass class ResponseAramConfigDO(BER_TLV_IE, tag=0xdf21, nested=[AramConfigDO]): # SEID v1.1 Table 4-5 pass class CommandStoreRefArDO(BER_TLV_IE, tag=0xf0, nested=[RefArDO]): # SEID v1.1 Table 5-2 pass class CommandDelete(BER_TLV_IE, tag=0xf1, nested=[AidRefDO, AidRefEmptyDO, RefDO, RefArDO]): # SEID v1.1 Table 5-4 pass class CommandUpdateRefreshTagDO(BER_TLV_IE, tag=0xf2): # SEID V1.1 Table 5-6 pass class CommandRegisterClientAidsDO(BER_TLV_IE, tag=0xf7, nested=[AidRefDO, AidRefEmptyDO]): # SEID v1.1 Table 5-7 pass class CommandGet(BER_TLV_IE, tag=0xf3, nested=[AidRefDO, AidRefEmptyDO]): # SEID v1.1 Table 5-8 pass class CommandGetAll(BER_TLV_IE, tag=0xf4): # SEID v1.1 Table 5-9 pass class CommandGetClientAidsDO(BER_TLV_IE, tag=0xf6): # SEID v1.1 Table 5-10 pass class CommandGetNext(BER_TLV_IE, tag=0xf5): # SEID v1.1 Table 5-11 pass class CommandGetDeviceConfigDO(BER_TLV_IE, tag=0xf8): # SEID v1.1 Table 5-12 pass class ResponseAracAidDO(BER_TLV_IE, tag=0xff70, nested=[AidRefDO, AidRefEmptyDO]): # SEID v1.1 Table 5-13 pass class BlockDO(BER_TLV_IE, tag=0xe7): # SEID v1.1 Table 6-13 _construct = Struct('offset'/Int16ub, 'length'/Int8ub) # SEID v1.1 Table 4-1 class GetCommandDoCollection(TLV_IE_Collection, nested=[RefDO, DeviceConfigDO]): pass # SEID v1.1 Table 4-2 class GetResponseDoCollection(TLV_IE_Collection, nested=[ResponseAllRefArDO, ResponseArDO, ResponseRefreshTagDO, ResponseAramConfigDO]): pass # SEID v1.1 Table 5-1 class StoreCommandDoCollection(TLV_IE_Collection, nested=[BlockDO, CommandStoreRefArDO, CommandDelete, CommandUpdateRefreshTagDO, CommandRegisterClientAidsDO, CommandGet, CommandGetAll, CommandGetClientAidsDO, CommandGetNext, CommandGetDeviceConfigDO]): pass # SEID v1.1 Section 5.1.2 class StoreResponseDoCollection(TLV_IE_Collection, nested=[ResponseAllRefArDO, ResponseAracAidDO, ResponseDeviceConfigDO]): pass class ADF_ARAM(CardADF): def __init__(self, aid='a00000015141434c00', name='ADF.ARA-M', fid=None, sfid=None, desc='ARA-M Application'): super().__init__(aid=aid, fid=fid, sfid=sfid, name=name, desc=desc) self.shell_commands += [self.AddlShellCommands()] files = [] self.add_files(files) def decode_select_response(self, data_hex): return pySim.global_platform.decode_select_response(data_hex) @staticmethod 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 and decoding the response data tlv.""" if cmd_do: cmd_do_enc = cmd_do.to_ie() cmd_do_len = len(cmd_do_enc) if cmd_do_len > 255: return ValueError('DO > 255 bytes not supported yet') else: cmd_do_enc = b'' cmd_do_len = 0 c_apdu = hdr + ('%02x' % cmd_do_len) + b2h(cmd_do_enc) (data, _sw) = tp.send_apdu_checksw(c_apdu, exp_sw) if data: if resp_cls: resp_do = resp_cls() resp_do.from_tlv(h2b(data)) return resp_do return data else: return None @staticmethod def store_data(tp, do) -> bytes: """Build the Command APDU for STORE DATA.""" return ADF_ARAM.xceive_apdu_tlv(tp, '80e29000', do, StoreResponseDoCollection) @staticmethod def get_all(tp): return ADF_ARAM.xceive_apdu_tlv(tp, '80caff40', None, GetResponseDoCollection) @staticmethod def get_config(tp, v_major=0, v_minor=0, v_patch=1): cmd_do = DeviceConfigDO() cmd_do.from_dict([{'device_interface_version_do': { 'major': v_major, 'minor': v_minor, 'patch': v_patch}}]) return ADF_ARAM.xceive_apdu_tlv(tp, '80cadf21', cmd_do, ResponseAramConfigDO) @with_default_category('Application-Specific Commands') class AddlShellCommands(CommandSet): def do_aram_get_all(self, _opts): """GET DATA [All] on the ARA-M Applet""" res_do = ADF_ARAM.get_all(self._cmd.lchan.scc._tp) if res_do: self._cmd.poutput_json(res_do.to_dict()) def do_aram_get_config(self, _opts): """Perform GET DATA [Config] on the ARA-M Applet: Tell it our version and retrieve its version.""" res_do = ADF_ARAM.get_config(self._cmd.lchan.scc._tp) if res_do: self._cmd.poutput_json(res_do.to_dict()) store_ref_ar_do_parse = argparse.ArgumentParser() # 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)') 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-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 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-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.add_argument('--nfc-always', action='store_true', help='NFC event access is allowed') 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) def do_aram_store_ref_ar_do(self, opts): """Perform STORE DATA [Command-Store-REF-AR-DO] to store a (new) access rule.""" # REF ref_do_content = [] if opts.aid is not None: ref_do_content += [{'aid_ref_do': opts.aid}] elif opts.aid_empty: ref_do_content += [{'aid_ref_empty_do': None}] ref_do_content += [{'dev_app_id_ref_do': opts.device_app_id}] if opts.pkg_ref: ref_do_content += [{'pkg_ref_do': {'package_name_string': opts.pkg_ref}}] # AR ar_do_content = [] if opts.apdu_never: ar_do_content += [{'apdu_ar_od': {'generic_access_rule': 'never'}}] elif opts.apdu_always: ar_do_content += [{'apdu_ar_do': {'generic_access_rule': 'always'}}] elif opts.apdu_filter: # TODO: multiple filters ar_do_content += [{'apdu_ar_do': {'apdu_filter': [opts.apdu_filter]}}] if opts.nfc_always: ar_do_content += [{'nfc_ar_do': {'nfc_event_access_rule': 'always'}}] elif opts.nfc_never: ar_do_content += [{'nfc_ar_do': {'nfc_event_access_rule': 'never'}}] if opts.android_permissions: ar_do_content += [{'perm_ar_do': {'permissions': opts.android_permissions}}] d = [{'ref_ar_do': [{'ref_do': ref_do_content}, {'ar_do': ar_do_content}]}] csrado = CommandStoreRefArDO() csrado.from_dict(d) res_do = ADF_ARAM.store_data(self._cmd.lchan.scc._tp, csrado) if res_do: self._cmd.poutput_json(res_do.to_dict()) def do_aram_delete_all(self, _opts): """Perform STORE DATA [Command-Delete[all]] to delete all access rules.""" deldo = CommandDelete() res_do = ADF_ARAM.store_data(self._cmd.lchan.scc._tp, deldo) if res_do: self._cmd.poutput_json(res_do.to_dict()) # SEAC v1.1 Section 4.1.2.2 + 5.1.2.2 sw_aram = { 'ARA-M': { '6381': 'Rule successfully stored but an access rule already exists', '6382': 'Rule successfully stored bu contained at least one unknown (discarded) BER-TLV', '6581': 'Memory Problem', '6700': 'Wrong Length in Lc', '6981': 'DO is not supported by the ARA-M/ARA-C', '6982': 'Security status not satisfied', '6984': 'Rules have been updated and must be read again / logical channels in use', '6985': 'Conditions not satisfied', '6a80': 'Incorrect values in the command data', '6a84': 'Rules have been updated and must be read again', '6a86': 'Incorrect P1 P2', '6a88': 'Referenced data not found', '6a89': 'Conflicting access rule already exists in the Secure Element', '6d00': 'Invalid instruction', '6e00': 'Invalid class', } } class CardApplicationARAM(CardApplication): def __init__(self): super().__init__('ARA-M', adf=ADF_ARAM(), sw=sw_aram)