Change in pysim[master]: ARA-M related command support

This is merely a historical archive of years 2008-2021, before the migration to mailman3.

A maintained and still updated list archive can be found at https://lists.osmocom.org/hyperkitty/list/gerrit-log@lists.osmocom.org/.

laforge gerrit-no-reply at lists.osmocom.org
Thu Nov 11 09:24:47 UTC 2021


laforge has submitted this change. ( https://gerrit.osmocom.org/c/pysim/+/25885 )

Change subject: ARA-M related command support
......................................................................

ARA-M related command support

This introduces support for talking to the ARA-M application on a card,
as specified in the GlobalPlatform "Secure Element Access Control"
specification v1.1.

Change-Id: Ia9107a4629c3d68320f32bbd4dd26e1f430717da
---
M docs/shell.rst
M pySim-shell.py
A pySim/ara_m.py
3 files changed, 454 insertions(+), 0 deletions(-)

Approvals:
  Jenkins Builder: Verified
  laforge: Looks good to me, approved
  herlesupreeth: Looks good to me, but someone else must approve



diff --git a/docs/shell.rst b/docs/shell.rst
index e5d70a0..3ab1113 100644
--- a/docs/shell.rst
+++ b/docs/shell.rst
@@ -443,6 +443,94 @@
    :func: ADF_USIM.AddlShellCommands.authenticate_parser
 
 
+ARA-M commands
+--------------
+
+The ARA-M commands exist to manage the access rules stored in an ARA-M applet on the card.
+
+ARA-M in the context of SIM cards is primarily used to enable Android UICC Carrier Privileges,
+please see https://source.android.com/devices/tech/config/uicc for more details on the background.
+
+
+aram_get_all
+~~~~~~~~~~~~
+
+Obtain and decode all access rules from the ARA-M applet on the card.
+
+NOTE: if the total size of the access rules exceeds 255 bytes, this command will fail, as
+it doesn't yet implement fragmentation/reassembly on rule retrieval. YMMV
+
+::
+
+  pySIM-shell (MF/ADF.ARA-M)> aram_get_all
+  [
+      {
+          "ResponseAllRefArDO": [
+              {
+                  "RefArDO": [
+                      {
+                          "RefDO": [
+                              {
+                                  "AidRefDO": "ffffffffffff"
+                              },
+                              {
+                                  "DevAppIdRefDO": "e46872f28b350b7e1f140de535c2a8d5804f0be3"
+                              }
+                          ]
+                      },
+                      {
+                          "ArDO": [
+                              {
+                                  "ApduArDO": {
+                                      "generic_access_rule": "always"
+                                  }
+                              },
+                              {
+                                  "PermArDO": {
+                                      "permissions": "0000000000000001"
+                                  }
+                              }
+                          ]
+                      }
+                  ]
+              }
+          ]
+      }
+  ]
+
+aram_get_config
+~~~~~~~~~~~~~~~
+Perform Config handshake with ARA-M applet: Tell it our version and retrieve its version.
+
+NOTE: Not supported in all ARA-M implementations.
+
+.. argparse::
+   :module: pySim.ara_m
+   :func: ADF_ARAM.AddlShellCommands.get_config_parser
+
+
+aram_store_ref_ar_do
+~~~~~~~~~~~~~~~~~~~~
+Store a [new] access rule on the ARA-M applet.
+
+.. argparse::
+   :module: pySim.ara_m
+   :func: ADF_ARAM.AddlShellCommands.store_ref_ar_do_parse
+
+For example, to store an Android UICC carrier privilege rule for the SHA1 hash of the certificate used to sign the CoIMS android app of Supreeth Herle (https://github.com/herlesupreeth/CoIMS_Wiki) you can use the following command:
+
+::
+
+  pySIM-shell (MF/ADF.ARA-M)> aram_store_ref_ar_do --aid FFFFFFFFFFFF --device-app-id E46872F28B350B7E1F140DE535C2A8D5804F0BE3 --android-permissions 0000000000000001 --apdu-always
+
+
+aram_delete_all
+~~~~~~~~~~~~~~~
+This command will request deletion of all access rules stored within the
+ARA-M applet.  Use it with caution, there is no undo.  Any rules later
+intended must be manually inserted again using `aram_store_ref_ar_do`
+
+
 
 cmd2 settable parameters
 ------------------------
diff --git a/pySim-shell.py b/pySim-shell.py
index 037b843..128c0ea 100755
--- a/pySim-shell.py
+++ b/pySim-shell.py
@@ -49,6 +49,7 @@
 from pySim.ts_102_221 import CardProfileUICC
 from pySim.ts_31_102 import CardApplicationUSIM
 from pySim.ts_31_103 import CardApplicationISIM
+from pySim.ara_m import CardApplicationARAM
 from pySim.gsm_r import DF_EIRENE
 
 # we need to import this module so that the SysmocomSJA2 sub-class of
@@ -86,6 +87,7 @@
 	profile = CardProfileUICC()
 	profile.add_application(CardApplicationUSIM())
 	profile.add_application(CardApplicationISIM())
+	profile.add_application(CardApplicationARAM())
 	rs = RuntimeState(card, profile)
 
 	# FIXME: do this dynamically
diff --git a/pySim/ara_m.py b/pySim/ara_m.py
new file mode 100644
index 0000000..5dee3e0
--- /dev/null
+++ b/pySim/ara_m.py
@@ -0,0 +1,364 @@
+# -*- 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 at 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)

-- 
To view, visit https://gerrit.osmocom.org/c/pysim/+/25885
To unsubscribe, or for help writing mail filters, visit https://gerrit.osmocom.org/settings

Gerrit-Project: pysim
Gerrit-Branch: master
Gerrit-Change-Id: Ia9107a4629c3d68320f32bbd4dd26e1f430717da
Gerrit-Change-Number: 25885
Gerrit-PatchSet: 2
Gerrit-Owner: laforge <laforge at osmocom.org>
Gerrit-Reviewer: Jenkins Builder
Gerrit-Reviewer: dexter <pmaier at sysmocom.de>
Gerrit-Reviewer: herlesupreeth <herlesupreeth at gmail.com>
Gerrit-Reviewer: laforge <laforge at osmocom.org>
Gerrit-MessageType: merged
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.osmocom.org/pipermail/gerrit-log/attachments/20211111/9d493e86/attachment.htm>


More information about the gerrit-log mailing list