mirror of https://gerrit.osmocom.org/pysim
pySim-shell: support TS 102 222 administrative commands
This adds support for creating/deleting and terminating files, as well as support for permanent card termination. Change-Id: I5b1ffb1334afa18d62beb642268066a30deb7ea6changes/26/27126/6
parent
747a978478
commit
3c9b784825
|
@ -49,6 +49,7 @@ from pySim.profile import CardProfile
|
|||
from pySim.ts_51_011 import CardProfileSIM, DF_TELECOM, DF_GSM
|
||||
from pySim.ts_102_221 import CardProfileUICC
|
||||
from pySim.ts_102_221 import CardProfileUICCSIM
|
||||
from pySim.ts_102_222 import Ts102222Commands
|
||||
from pySim.ts_31_102 import CardApplicationUSIM
|
||||
from pySim.ts_31_103 import CardApplicationISIM
|
||||
from pySim.ara_m import CardApplicationARAM
|
||||
|
@ -174,6 +175,7 @@ class PysimApp(cmd2.Cmd):
|
|||
'conserve_write', False, self.conserve_write)
|
||||
self._onchange_apdu_trace('apdu_trace', False, self.apdu_trace)
|
||||
self.register_command_set(Iso7816Commands())
|
||||
self.register_command_set(Ts102222Commands())
|
||||
self.register_command_set(PySimCommands())
|
||||
self.iccid, sw = self.card.read_iccid()
|
||||
rs.select('MF', self)
|
||||
|
|
|
@ -448,6 +448,26 @@ class SimCardCommands(object):
|
|||
"""
|
||||
return self._tp.send_apdu_checksw(self.cla_byte + '44000002' + fid)
|
||||
|
||||
def create_file(self, payload: Hexstr):
|
||||
"""Execute CREEATE FILE command as per TS 102 222 Section 6.3"""
|
||||
return self._tp.send_apdu_checksw(self.cla_byte + 'e00000%02x%s' % (len(payload)//2, payload))
|
||||
|
||||
def delete_file(self, fid):
|
||||
"""Execute DELETE FILE command as per TS 102 222 Section 6.4"""
|
||||
return self._tp.send_apdu_checksw(self.cla_byte + 'e4000002' + fid)
|
||||
|
||||
def terminate_df(self, fid):
|
||||
"""Execute TERMINATE DF command as per TS 102 222 Section 6.7"""
|
||||
return self._tp.send_apdu_checksw(self.cla_byte + 'e6000002' + fid)
|
||||
|
||||
def terminate_ef(self, fid):
|
||||
"""Execute TERMINATE EF command as per TS 102 222 Section 6.8"""
|
||||
return self._tp.send_apdu_checksw(self.cla_byte + 'e8000002' + fid)
|
||||
|
||||
def terminate_card_usage(self):
|
||||
"""Execute TERMINATE CARD USAGE command as per TS 102 222 Section 6.9"""
|
||||
return self._tp.send_apdu_checksw(self.cla_byte + 'fe000000')
|
||||
|
||||
def manage_channel(self, mode='open', lchan_nr=0):
|
||||
"""Execute MANAGE CHANNEL command as per TS 102 221 Section 11.1.17.
|
||||
|
||||
|
|
|
@ -1475,6 +1475,10 @@ class RuntimeState(object):
|
|||
(data, sw) = self.card._scc.status()
|
||||
return self.selected_file.decode_select_response(data)
|
||||
|
||||
def get_file_for_selectable(self, name: str):
|
||||
sels = self.selected_file.get_selectables()
|
||||
return sels[name]
|
||||
|
||||
def activate_file(self, name: str):
|
||||
"""Request ACTIVATE FILE of specified file."""
|
||||
sels = self.selected_file.get_selectables()
|
||||
|
|
|
@ -107,11 +107,11 @@ class FileDescriptor(BER_TLV_IE, tag=0x82):
|
|||
|
||||
# ETSI TS 102 221 11.1.1.4.4
|
||||
class FileIdentifier(BER_TLV_IE, tag=0x83):
|
||||
_construct = GreedyBytes
|
||||
_construct = HexAdapter(GreedyBytes)
|
||||
|
||||
# ETSI TS 102 221 11.1.1.4.5
|
||||
class DfName(BER_TLV_IE, tag=0x84):
|
||||
_construct = GreedyBytes
|
||||
_construct = HexAdapter(GreedyBytes)
|
||||
|
||||
# ETSI TS 102 221 11.1.1.4.6.1
|
||||
class UiccCharacteristics(BER_TLV_IE, tag=0x80):
|
||||
|
@ -157,12 +157,18 @@ class SpecificUiccEnvironmentConditions(BER_TLV_IE, tag=0x88):
|
|||
class Platform2PlatformCatSecuredApdu(BER_TLV_IE, tag=0x89):
|
||||
_construct = GreedyBytes
|
||||
|
||||
# sysmoISIM-SJA2 specific
|
||||
class ToolkitAccessConditions(BER_TLV_IE, tag=0xD2):
|
||||
_construct = FlagsEnum(Byte, rfm_create=1, rfm_delete_terminate=2, other_applet_create=4,
|
||||
other_applet_delete_terminate=8)
|
||||
|
||||
# ETSI TS 102 221 11.1.1.4.6.0
|
||||
class ProprietaryInformation(BER_TLV_IE, tag=0xA5,
|
||||
nested=[UiccCharacteristics, ApplicationPowerConsumption,
|
||||
MinApplicationClockFrequency, AvailableMemory,
|
||||
FileDetails, ReservedFileSize, MaximumFileSize,
|
||||
SupportedFilesystemCommands, SpecificUiccEnvironmentConditions]):
|
||||
SupportedFilesystemCommands, SpecificUiccEnvironmentConditions,
|
||||
ToolkitAccessConditions]):
|
||||
pass
|
||||
|
||||
# ETSI TS 102 221 11.1.1.4.7.1
|
||||
|
@ -176,11 +182,11 @@ class SecurityAttribExpanded(BER_TLV_IE, tag=0xab):
|
|||
# ETSI TS 102 221 11.1.1.4.7.3
|
||||
class SecurityAttribReferenced(BER_TLV_IE, tag=0x8b):
|
||||
# TODO: longer format with SEID
|
||||
_construct = Struct('ef_arr_file_id'/Bytes(2), 'ef_arr_record_nr'/Int8ub)
|
||||
_construct = Struct('ef_arr_file_id'/HexAdapter(Bytes(2)), 'ef_arr_record_nr'/Int8ub)
|
||||
|
||||
# ETSI TS 102 221 11.1.1.4.8
|
||||
class ShortFileIdentifier(BER_TLV_IE, tag=0x88):
|
||||
_construct = Byte
|
||||
_construct = HexAdapter(COptional(Bytes(1)))
|
||||
|
||||
# ETSI TS 102 221 11.1.1.4.9
|
||||
class LifeCycleStatusInteger(BER_TLV_IE, tag=0x8A):
|
||||
|
@ -202,6 +208,23 @@ class LifeCycleStatusInteger(BER_TLV_IE, tag=0x8A):
|
|||
ret = lcsi
|
||||
self.decoded = ret
|
||||
return self.decoded
|
||||
def _to_bytes(self):
|
||||
if self.decoded == 'no_information':
|
||||
return b'\x00'
|
||||
elif self.decoded == 'creation':
|
||||
return b'\x01'
|
||||
elif self.decoded == 'initialization':
|
||||
return b'\x03'
|
||||
elif self.decoded == 'operational_activated':
|
||||
return b'\x05'
|
||||
elif self.decoded == 'operational_deactivated':
|
||||
return b'\x04'
|
||||
elif self.decoded == 'termination':
|
||||
return b'\x0c'
|
||||
elif isinstance(self.decoded, int):
|
||||
return self.decoded.to_bytes(1, 'big')
|
||||
else:
|
||||
raise ValueError
|
||||
|
||||
# ETSI TS 102 221 11.1.1.4.9
|
||||
class PS_DO(BER_TLV_IE, tag=0x90):
|
||||
|
|
|
@ -0,0 +1,206 @@
|
|||
#!/usr/bin/env python3
|
||||
|
||||
# Interactive shell for working with SIM / UICC / USIM / ISIM cards
|
||||
#
|
||||
# (C) 2022 by 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 typing import List
|
||||
|
||||
import cmd2
|
||||
from cmd2 import style, fg, bg
|
||||
from cmd2 import CommandSet, with_default_category, with_argparser
|
||||
import argparse
|
||||
|
||||
from pySim.ts_31_102 import EF_UST_map, EF_USIM_ADF_map
|
||||
from pySim.ts_31_103 import EF_IST_map, EF_ISIM_ADF_map
|
||||
|
||||
from pySim.exceptions import *
|
||||
from pySim.utils import h2b, swap_nibbles, b2h, JsonEncoder
|
||||
|
||||
from pySim.ts_102_221 import *
|
||||
|
||||
@with_default_category('TS 102 222 Administrative Commands')
|
||||
class Ts102222Commands(CommandSet):
|
||||
"""Administrative commands for telecommunication applications."""
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
|
||||
delfile_parser = argparse.ArgumentParser()
|
||||
delfile_parser.add_argument('--force-delete', action='store_true',
|
||||
help='I really want to permanently delete the file. I know pySim cannot re-create it yet!')
|
||||
delfile_parser.add_argument('NAME', type=str, help='File name or FID to delete')
|
||||
|
||||
@cmd2.with_argparser(delfile_parser)
|
||||
def do_delete_file(self, opts):
|
||||
"""Delete the specified file. DANGEROUS! See TS 102 222 Section 6.4.
|
||||
This will permanently delete the specified file from the card.
|
||||
pySim has no support to re-create files yet, and even if it did, your card may not allow it!"""
|
||||
if not opts.force_delete:
|
||||
self._cmd.perror("Refusing to permanently delete the file, please read the help text.")
|
||||
return
|
||||
f = self._cmd.rs.get_file_for_selectable(opts.NAME)
|
||||
(data, sw) = self._cmd.card._scc.delete_file(f.fid)
|
||||
|
||||
def complete_delete_file(self, text, line, begidx, endidx) -> List[str]:
|
||||
"""Command Line tab completion for DELETE FILE"""
|
||||
index_dict = {1: self._cmd.rs.selected_file.get_selectable_names()}
|
||||
return self._cmd.index_based_complete(text, line, begidx, endidx, index_dict=index_dict)
|
||||
|
||||
termdf_parser = argparse.ArgumentParser()
|
||||
termdf_parser.add_argument('--force', action='store_true',
|
||||
help='I really want to terminate the file. I know I can not recover from it!')
|
||||
termdf_parser.add_argument('NAME', type=str, help='File name or FID')
|
||||
|
||||
@cmd2.with_argparser(termdf_parser)
|
||||
def do_terminate_df(self, opts):
|
||||
"""Terminate the specified DF. DANGEROUS! See TS 102 222 6.7.
|
||||
This is a permanent, one-way operation on the card. There is no undo, you can not recover
|
||||
a terminated DF. The only permitted command for a terminated DF is the DLETE FILE command."""
|
||||
if not opts.force:
|
||||
self._cmd.perror("Refusing to terminate the file, please read the help text.")
|
||||
return
|
||||
f = self._cmd.rs.get_file_for_selectable(opts.NAME)
|
||||
(data, sw) = self._cmd.card._scc.terminate_df(f.fid)
|
||||
|
||||
def complete_terminate_df(self, text, line, begidx, endidx) -> List[str]:
|
||||
"""Command Line tab completion for TERMINATE DF"""
|
||||
index_dict = {1: self._cmd.rs.selected_file.get_selectable_names()}
|
||||
return self._cmd.index_based_complete(text, line, begidx, endidx, index_dict=index_dict)
|
||||
|
||||
@cmd2.with_argparser(termdf_parser)
|
||||
def do_terminate_ef(self, opts):
|
||||
"""Terminate the specified EF. DANGEROUS! See TS 102 222 6.8.
|
||||
This is a permanent, one-way operation on the card. There is no undo, you can not recover
|
||||
a terminated EF. The only permitted command for a terminated EF is the DLETE FILE command."""
|
||||
if not opts.force:
|
||||
self._cmd.perror("Refusing to terminate the file, please read the help text.")
|
||||
return
|
||||
f = self._cmd.rs.get_file_for_selectable(opts.NAME)
|
||||
(data, sw) = self._cmd.card._scc.terminate_ef(f.fid)
|
||||
|
||||
def complete_terminate_ef(self, text, line, begidx, endidx) -> List[str]:
|
||||
"""Command Line tab completion for TERMINATE EF"""
|
||||
index_dict = {1: self._cmd.rs.selected_file.get_selectable_names()}
|
||||
return self._cmd.index_based_complete(text, line, begidx, endidx, index_dict=index_dict)
|
||||
|
||||
tcard_parser = argparse.ArgumentParser()
|
||||
tcard_parser.add_argument('--force-terminate-card', action='store_true',
|
||||
help='I really want to permanently terminate the card. It will not be usable afterwards!')
|
||||
|
||||
@cmd2.with_argparser(tcard_parser)
|
||||
def do_terminate_card_usage(self, opts):
|
||||
"""Terminate the Card. SUPER DANGEROUS! See TS 102 222 Section 6.9.
|
||||
This will permanently brick the card and can NOT be recovered from!"""
|
||||
if not opts.force_terminate_card:
|
||||
self._cmd.perror("Refusing to permanently terminate the card, please read the help text.")
|
||||
return
|
||||
(data, sw) = self._cmd.card._scc.terminate_card_usage()
|
||||
|
||||
create_parser = argparse.ArgumentParser()
|
||||
create_parser.add_argument('FILE_ID', type=str, help='File Identifier as 4-character hex string')
|
||||
create_parser._action_groups.pop()
|
||||
create_required = create_parser.add_argument_group('required arguments')
|
||||
create_optional = create_parser.add_argument_group('optional arguments')
|
||||
create_required.add_argument('--ef-arr-file-id', required=True, type=str, help='Referenced Security: File Identifier of EF.ARR')
|
||||
create_required.add_argument('--ef-arr-record-nr', required=True, type=int, help='Referenced Security: Record Number within EF.ARR')
|
||||
create_required.add_argument('--file-size', required=True, type=int, help='Size of file in octets')
|
||||
create_required.add_argument('--structure', required=True, type=str, choices=['transparent', 'linear_fixed', 'ber_tlv'],
|
||||
help='Structure of the to-be-created EF')
|
||||
create_optional.add_argument('--short-file-id', type=str, help='Short File Identifier as 2-digit hex string')
|
||||
create_optional.add_argument('--shareable', action='store_true', help='Should the file be shareable?')
|
||||
create_optional.add_argument('--record-length', type=int, help='Length of each record in octets')
|
||||
|
||||
@cmd2.with_argparser(create_parser)
|
||||
def do_create_ef(self, opts):
|
||||
"""Create a new EF below the currently selected DF. Requires related privileges."""
|
||||
file_descriptor = {
|
||||
'file_descriptor_byte': {
|
||||
'shareable': opts.shareable,
|
||||
'file_type': 'working_ef',
|
||||
'structure': opts.structure,
|
||||
}
|
||||
}
|
||||
if opts.structure == 'linear_fixed':
|
||||
if not opts.record_length:
|
||||
self._cmd.perror("you must specify the --record-length for linear fixed EF")
|
||||
return
|
||||
file_descriptor['record_len'] = opts.record_length
|
||||
elif opts.structure == 'ber_tlv':
|
||||
self._cmd.perror("BER-TLV creation not yet fully supported, sorry")
|
||||
return
|
||||
ies = [FileDescriptor(decoded=file_descriptor), FileIdentifier(decoded=opts.FILE_ID),
|
||||
LifeCycleStatusInteger(decoded='operational_activated'),
|
||||
SecurityAttribReferenced(decoded={'ef_arr_file_id': opts.ef_arr_file_id,
|
||||
'ef_arr_record_nr': opts.ef_arr_record_nr }),
|
||||
FileSize(decoded=opts.file_size),
|
||||
ShortFileIdentifier(decoded=opts.short_file_id),
|
||||
]
|
||||
fcp = FcpTemplate(children=ies)
|
||||
(data, sw) = self._cmd.card._scc.create_file(b2h(fcp.to_tlv()))
|
||||
# the newly-created file is automatically selected but our runtime state knows nothing of it
|
||||
self._cmd.rs.select_file(self._cmd.rs.selected_file)
|
||||
|
||||
createdf_parser = argparse.ArgumentParser()
|
||||
createdf_parser.add_argument('FILE_ID', type=str, help='File Identifier as 4-character hex string')
|
||||
createdf_parser._action_groups.pop()
|
||||
createdf_required = createdf_parser.add_argument_group('required arguments')
|
||||
createdf_optional = createdf_parser.add_argument_group('optional arguments')
|
||||
createdf_sja_optional = createdf_parser.add_argument_group('sysmoISIM-SJA optional arguments')
|
||||
createdf_required.add_argument('--ef-arr-file-id', required=True, type=str, help='Referenced Security: File Identifier of EF.ARR')
|
||||
createdf_required.add_argument('--ef-arr-record-nr', required=True, type=int, help='Referenced Security: Record Number within EF.ARR')
|
||||
createdf_optional.add_argument('--shareable', action='store_true', help='Should the file be shareable?')
|
||||
createdf_optional.add_argument('--aid', type=str, help='Application ID (creates an ADF, instead of a DF)')
|
||||
# mandatory by spec, but ignored by several OS, so don't force the user
|
||||
createdf_optional.add_argument('--total-file-size', type=int, help='Physical memory allocated for DF/ADi in octets')
|
||||
createdf_sja_optional.add_argument('--permit-rfm-create', action='store_true')
|
||||
createdf_sja_optional.add_argument('--permit-rfm-delete-terminate', action='store_true')
|
||||
createdf_sja_optional.add_argument('--permit-other-applet-create', action='store_true')
|
||||
createdf_sja_optional.add_argument('--permit-other-applet-delete-terminate', action='store_true')
|
||||
|
||||
@cmd2.with_argparser(createdf_parser)
|
||||
def do_create_df(self, opts):
|
||||
"""Create a new DF below the currently selected DF. Requires related privileges."""
|
||||
file_descriptor = {
|
||||
'file_descriptor_byte': {
|
||||
'shareable': opts.shareable,
|
||||
'file_type': 'df',
|
||||
'structure': 'no_info_given',
|
||||
}
|
||||
}
|
||||
ies = []
|
||||
ies.append(FileDescriptor(decoded=file_descriptor))
|
||||
ies.append(FileIdentifier(decoded=opts.FILE_ID))
|
||||
if opts.aid:
|
||||
ies.append(DfName(decoded=opts.aid))
|
||||
ies.append(LifeCycleStatusInteger(decoded='operational_activated'))
|
||||
ies.append(SecurityAttribReferenced(decoded={'ef_arr_file_id': opts.ef_arr_file_id,
|
||||
'ef_arr_record_nr': opts.ef_arr_record_nr }))
|
||||
if opts.total_file_size:
|
||||
ies.append(TotalFileSize(decoded=opts.total_file_size))
|
||||
# TODO: Spec states PIN Status Template DO is mandatory
|
||||
if opts.permit_rfm_create or opts.permit_rfm_delete_terminate or opts.permit_other_applet_create or opts.permit_other_applet_delete_terminate:
|
||||
toolkit_ac = {
|
||||
'rfm_create': opts.permit_rfm_create,
|
||||
'rfm_delete_terminate': opts.permit_rfm_delete_terminate,
|
||||
'other_applet_create': opts.permit_other_applet_create,
|
||||
'other_applet_delete_terminate': opts.permit_other_applet_delete_terminate,
|
||||
}
|
||||
ies.append(ProprietaryInformation(children=[ToolkitAccessConditions(decoded=toolkit_ac)]))
|
||||
fcp = FcpTemplate(children=ies)
|
||||
(data, sw) = self._cmd.card._scc.create_file(b2h(fcp.to_tlv()))
|
||||
# the newly-created file is automatically selected but our runtime state knows nothing of it
|
||||
self._cmd.rs.select_file(self._cmd.rs.selected_file)
|
Loading…
Reference in New Issue