mirror of
https://gitea.osmocom.org/sim-card/pysim.git
synced 2026-03-16 18:38:32 +03:00
We had a mixture of tab and 4space based indenting, which is a bad idea. 4space is the standard in python, so convert all our code to that. The result unfortuantely still shoed even more inconsistencies, so I've decided to run autopep8 on the entire code base. Change-Id: I4a4b1b444a2f43fab05fc5d2c8a7dd6ddecb5f07
413 lines
14 KiB
Python
413 lines
14 KiB
Python
# -*- 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 <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 construct import *
|
|
from construct import Optional as COptional
|
|
from pySim.construct import *
|
|
from pySim.filesystem import *
|
|
from pySim.tlv import *
|
|
|
|
# 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
|
|
elif do[0] == 0x01:
|
|
self.decoded = {'generic_access_rule': 'always'}
|
|
return self.decoded
|
|
else:
|
|
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'
|
|
elif self.decoded['generic_access_rule'] == 'always':
|
|
return b'\x01'
|
|
else:
|
|
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
|
|
_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)
|
|
|
|
@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
|
|
else:
|
|
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([{'DeviceInterfaceVersionDO': {
|
|
'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 __init(self):
|
|
super().__init__()
|
|
|
|
def do_aram_get_all(self, opts):
|
|
"""GET DATA [All] on the ARA-M Applet"""
|
|
res_do = ADF_ARAM.get_all(self._cmd.card._scc._tp)
|
|
if res_do:
|
|
self._cmd.poutput_json(res_do.to_dict())
|
|
|
|
def do_aram_get_config(self, opts):
|
|
"""GET DATA [Config] on the ARA-M Applet"""
|
|
res_do = ADF_ARAM.get_config(self._cmd.card._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:
|
|
ref_do_content += [{'AidRefDO': opts.aid}]
|
|
elif opts.aid_empty:
|
|
ref_do_content += [{'AidRefEmptyDO': None}]
|
|
ref_do_content += [{'DevAppIdRefDO': opts.device_app_id}]
|
|
if opts.pkg_ref:
|
|
ref_do_content += [{'PkgRefDO': opts.pkg_ref}]
|
|
# AR
|
|
ar_do_content = []
|
|
if opts.apdu_never:
|
|
ar_do_content += [{'ApduArDO': {'generic_access_rule': 'never'}}]
|
|
elif opts.apdu_always:
|
|
ar_do_content += [{'ApduArDO': {'generic_access_rule': 'always'}}]
|
|
elif opts.apdu_filter:
|
|
# TODO: multiple filters
|
|
ar_do_content += [{'ApduArDO': {'apdu_filter': [opts.apdu_filter]}}]
|
|
if opts.nfc_always:
|
|
ar_do_content += [{'NfcArDO': {'nfc_event_access_rule': 'always'}}]
|
|
elif opts.nfc_never:
|
|
ar_do_content += [{'NfcArDO': {'nfc_event_access_rule': 'never'}}]
|
|
if opts.android_permissions:
|
|
ar_do_content += [{'PermArDO': {'permissions': opts.android_permissions}}]
|
|
d = [{'RefArDO': [{'RefDO': ref_do_content}, {'ArDO': ar_do_content}]}]
|
|
csrado = CommandStoreRefArDO()
|
|
csrado.from_dict(d)
|
|
res_do = ADF_ARAM.store_data(self._cmd.card._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.card._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)
|