2021-10-20 16:40:54 +00:00
# -*- 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)
2022-02-10 17:05:45 +00:00
2021-10-20 16:40:54 +00:00
class AidRefDO ( BER_TLV_IE , tag = 0x4f ) :
# SEID v1.1 Table 6-3
_construct = HexAdapter ( GreedyBytes )
2022-02-10 17:05:45 +00:00
2021-10-20 16:40:54 +00:00
class AidRefEmptyDO ( BER_TLV_IE , tag = 0xc0 ) :
# SEID v1.1 Table 6-3
pass
2022-02-10 17:05:45 +00:00
2021-10-20 16:40:54 +00:00
class DevAppIdRefDO ( BER_TLV_IE , tag = 0xc1 ) :
# SEID v1.1 Table 6-4
_construct = HexAdapter ( GreedyBytes )
2022-02-10 17:05:45 +00:00
2021-10-20 16:40:54 +00:00
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 " ) )
2022-02-10 17:05:45 +00:00
class RefDO ( BER_TLV_IE , tag = 0xe1 , nested = [ AidRefDO , AidRefEmptyDO , DevAppIdRefDO , PkgRefDO ] ) :
2021-10-20 16:40:54 +00:00
# SEID v1.1 Table 6-5
pass
2022-02-10 17:05:45 +00:00
2021-10-20 16:40:54 +00:00
class ApduArDO ( BER_TLV_IE , tag = 0xd0 ) :
# SEID v1.1 Table 6-8
2022-02-10 17:05:45 +00:00
def _from_bytes ( self , do : bytes ) :
2021-10-20 16:40:54 +00:00
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
2022-02-10 17:05:45 +00:00
2021-10-20 16:40:54 +00:00
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
2022-02-10 17:05:45 +00:00
2021-10-20 16:40:54 +00:00
class NfcArDO ( BER_TLV_IE , tag = 0xd1 ) :
# SEID v1.1 Table 6-9
2022-02-10 17:05:45 +00:00
_construct = Struct ( ' nfc_event_access_rule ' /
Enum ( Int8ub , never = 0 , always = 1 ) )
2021-10-20 16:40:54 +00:00
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 ) ) )
2022-02-10 17:05:45 +00:00
2021-10-20 16:40:54 +00:00
class ArDO ( BER_TLV_IE , tag = 0xe3 , nested = [ ApduArDO , NfcArDO , PermArDO ] ) :
# SEID v1.1 Table 6-7
pass
2022-02-10 17:05:45 +00:00
2021-10-20 16:40:54 +00:00
class RefArDO ( BER_TLV_IE , tag = 0xe2 , nested = [ RefDO , ArDO ] ) :
# SEID v1.1 Table 6-6
pass
2022-02-10 17:05:45 +00:00
2021-10-20 16:40:54 +00:00
class ResponseAllRefArDO ( BER_TLV_IE , tag = 0xff40 , nested = [ RefArDO ] ) :
# SEID v1.1 Table 4-2
pass
2022-02-10 17:05:45 +00:00
2021-10-20 16:40:54 +00:00
class ResponseArDO ( BER_TLV_IE , tag = 0xff50 , nested = [ ArDO ] ) :
# SEID v1.1 Table 4-3
pass
2022-02-10 17:05:45 +00:00
2021-10-20 16:40:54 +00:00
class ResponseRefreshTagDO ( BER_TLV_IE , tag = 0xdf20 ) :
# SEID v1.1 Table 4-4
_construct = Struct ( ' refresh_tag ' / HexAdapter ( Bytes ( 8 ) ) )
2022-02-10 17:05:45 +00:00
2021-10-20 16:40:54 +00:00
class DeviceInterfaceVersionDO ( BER_TLV_IE , tag = 0xe6 ) :
# SEID v1.1 Table 6-12
_construct = Struct ( ' major ' / Int8ub , ' minor ' / Int8ub , ' patch ' / Int8ub )
2022-02-10 17:05:45 +00:00
2021-10-20 16:40:54 +00:00
class DeviceConfigDO ( BER_TLV_IE , tag = 0xe4 , nested = [ DeviceInterfaceVersionDO ] ) :
# SEID v1.1 Table 6-10
pass
2022-02-10 17:05:45 +00:00
2021-10-20 16:40:54 +00:00
class ResponseDeviceConfigDO ( BER_TLV_IE , tag = 0xff7f , nested = [ DeviceConfigDO ] ) :
# SEID v1.1 Table 5-14
pass
2022-02-10 17:05:45 +00:00
2021-10-20 16:40:54 +00:00
class AramConfigDO ( BER_TLV_IE , tag = 0xe5 , nested = [ DeviceInterfaceVersionDO ] ) :
# SEID v1.1 Table 6-11
pass
2022-02-10 17:05:45 +00:00
2021-10-20 16:40:54 +00:00
class ResponseAramConfigDO ( BER_TLV_IE , tag = 0xdf21 , nested = [ AramConfigDO ] ) :
# SEID v1.1 Table 4-5
pass
2022-02-10 17:05:45 +00:00
2021-10-20 16:40:54 +00:00
class CommandStoreRefArDO ( BER_TLV_IE , tag = 0xf0 , nested = [ RefArDO ] ) :
# SEID v1.1 Table 5-2
pass
2022-02-10 17:05:45 +00:00
2021-10-20 16:40:54 +00:00
class CommandDelete ( BER_TLV_IE , tag = 0xf1 , nested = [ AidRefDO , AidRefEmptyDO , RefDO , RefArDO ] ) :
# SEID v1.1 Table 5-4
pass
2022-02-10 17:05:45 +00:00
2021-10-20 16:40:54 +00:00
class CommandUpdateRefreshTagDO ( BER_TLV_IE , tag = 0xf2 ) :
# SEID V1.1 Table 5-6
pass
2022-02-10 17:05:45 +00:00
2021-10-20 16:40:54 +00:00
class CommandRegisterClientAidsDO ( BER_TLV_IE , tag = 0xf7 , nested = [ AidRefDO , AidRefEmptyDO ] ) :
# SEID v1.1 Table 5-7
pass
2022-02-10 17:05:45 +00:00
2021-10-20 16:40:54 +00:00
class CommandGet ( BER_TLV_IE , tag = 0xf3 , nested = [ AidRefDO , AidRefEmptyDO ] ) :
# SEID v1.1 Table 5-8
pass
2022-02-10 17:05:45 +00:00
2021-10-20 16:40:54 +00:00
class CommandGetAll ( BER_TLV_IE , tag = 0xf4 ) :
# SEID v1.1 Table 5-9
pass
2022-02-10 17:05:45 +00:00
2021-10-20 16:40:54 +00:00
class CommandGetClientAidsDO ( BER_TLV_IE , tag = 0xf6 ) :
# SEID v1.1 Table 5-10
pass
2022-02-10 17:05:45 +00:00
2021-10-20 16:40:54 +00:00
class CommandGetNext ( BER_TLV_IE , tag = 0xf5 ) :
# SEID v1.1 Table 5-11
pass
2022-02-10 17:05:45 +00:00
2021-10-20 16:40:54 +00:00
class CommandGetDeviceConfigDO ( BER_TLV_IE , tag = 0xf8 ) :
# SEID v1.1 Table 5-12
pass
2022-02-10 17:05:45 +00:00
2021-10-20 16:40:54 +00:00
class ResponseAracAidDO ( BER_TLV_IE , tag = 0xff70 , nested = [ AidRefDO , AidRefEmptyDO ] ) :
# SEID v1.1 Table 5-13
pass
2022-02-10 17:05:45 +00:00
2021-10-20 16:40:54 +00:00
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
2022-02-10 17:05:45 +00:00
2021-10-20 16:40:54 +00:00
class GetResponseDoCollection ( TLV_IE_Collection , nested = [ ResponseAllRefArDO , ResponseArDO ,
ResponseRefreshTagDO , ResponseAramConfigDO ] ) :
pass
# SEID v1.1 Table 5-1
2022-02-10 17:05:45 +00:00
2021-10-20 16:40:54 +00:00
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
2022-02-10 17:05:45 +00:00
2021-10-20 16:40:54 +00:00
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
2022-02-10 17:05:45 +00:00
def xceive_apdu_tlv ( tp , hdr : Hexstr , cmd_do , resp_cls , exp_sw = ' 9000 ' ) :
2021-10-20 16:40:54 +00:00
""" 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 ( )
2022-02-10 17:05:45 +00:00
cmd_do . from_dict ( [ { ' DeviceInterfaceVersionDO ' : {
' major ' : v_major , ' minor ' : v_minor , ' patch ' : v_patch } } ] )
2021-10-20 16:40:54 +00:00
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 ) :
2022-02-15 15:39:08 +00:00
""" Perform GET DATA [Config] on the ARA-M Applet: Tell it our version and retrieve its version. """
2021-10-20 16:40:54 +00:00
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
2022-02-10 17:05:45 +00:00
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) ' )
2021-10-20 16:40:54 +00:00
aid_grp = store_ref_ar_do_parse . add_mutually_exclusive_group ( )
2022-02-10 17:05:45 +00:00
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) ' )
2021-10-20 16:40:54 +00:00
# AR-DO
apdu_grp = store_ref_ar_do_parse . add_mutually_exclusive_group ( )
2022-02-10 17:05:45 +00:00
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) ' )
2021-10-20 16:40:54 +00:00
nfc_grp = store_ref_ar_do_parse . add_mutually_exclusive_group ( )
2022-02-10 17:05:45 +00:00
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) ' )
2021-10-20 16:40:54 +00:00
@cmd2.with_argparser ( store_ref_ar_do_parse )
def do_aram_store_ref_ar_do ( self , opts ) :
2022-02-15 15:39:08 +00:00
""" Perform STORE DATA [Command-Store-REF-AR-DO] to store a (new) access rule. """
2021-10-20 16:40:54 +00:00
# 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 } } ]
2022-02-10 17:05:45 +00:00
d = [ { ' RefArDO ' : [ { ' RefDO ' : ref_do_content } , { ' ArDO ' : ar_do_content } ] } ]
2021-10-20 16:40:54 +00:00
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 ' ,
}
}
2022-02-10 17:05:45 +00:00
2021-10-20 16:40:54 +00:00
class CardApplicationARAM ( CardApplication ) :
def __init__ ( self ) :
2022-02-10 17:05:45 +00:00
super ( ) . __init__ ( ' ARA-M ' , adf = ADF_ARAM ( ) , sw = sw_aram )