blob: a4ea45344128b05ada0bd238bec8060d5c43cea7 [file] [log] [blame]
Harald Welte3c9b7842021-10-19 21:44:24 +02001#!/usr/bin/env python3
2
3# Interactive shell for working with SIM / UICC / USIM / ISIM cards
4#
5# (C) 2022 by Harald Welte <laforge@osmocom.org>
6#
7# This program is free software: you can redistribute it and/or modify
8# it under the terms of the GNU General Public License as published by
9# the Free Software Foundation, either version 2 of the License, or
10# (at your option) any later version.
11#
12# This program is distributed in the hope that it will be useful,
13# but WITHOUT ANY WARRANTY; without even the implied warranty of
14# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15# GNU General Public License for more details.
16#
17# You should have received a copy of the GNU General Public License
18# along with this program. If not, see <http://www.gnu.org/licenses/>.
19
20from typing import List
21
22import cmd2
Harald Welte3c9b7842021-10-19 21:44:24 +020023from cmd2 import CommandSet, with_default_category, with_argparser
24import argparse
25
Harald Welte3c9b7842021-10-19 21:44:24 +020026from pySim.exceptions import *
27from pySim.utils import h2b, swap_nibbles, b2h, JsonEncoder
28
29from pySim.ts_102_221 import *
30
31@with_default_category('TS 102 222 Administrative Commands')
32class Ts102222Commands(CommandSet):
33 """Administrative commands for telecommunication applications."""
34
35 def __init__(self):
36 super().__init__()
37
38 delfile_parser = argparse.ArgumentParser()
39 delfile_parser.add_argument('--force-delete', action='store_true',
40 help='I really want to permanently delete the file. I know pySim cannot re-create it yet!')
41 delfile_parser.add_argument('NAME', type=str, help='File name or FID to delete')
42
43 @cmd2.with_argparser(delfile_parser)
44 def do_delete_file(self, opts):
45 """Delete the specified file. DANGEROUS! See TS 102 222 Section 6.4.
46 This will permanently delete the specified file from the card.
47 pySim has no support to re-create files yet, and even if it did, your card may not allow it!"""
48 if not opts.force_delete:
49 self._cmd.perror("Refusing to permanently delete the file, please read the help text.")
50 return
Harald Weltea6c0f882022-07-17 14:23:17 +020051 f = self._cmd.lchan.get_file_for_selectable(opts.NAME)
Harald Welte3c9b7842021-10-19 21:44:24 +020052 (data, sw) = self._cmd.card._scc.delete_file(f.fid)
53
54 def complete_delete_file(self, text, line, begidx, endidx) -> List[str]:
55 """Command Line tab completion for DELETE FILE"""
Harald Weltea6c0f882022-07-17 14:23:17 +020056 index_dict = {1: self._cmd.lchan.selected_file.get_selectable_names()}
Harald Welte3c9b7842021-10-19 21:44:24 +020057 return self._cmd.index_based_complete(text, line, begidx, endidx, index_dict=index_dict)
58
59 termdf_parser = argparse.ArgumentParser()
60 termdf_parser.add_argument('--force', action='store_true',
61 help='I really want to terminate the file. I know I can not recover from it!')
62 termdf_parser.add_argument('NAME', type=str, help='File name or FID')
63
64 @cmd2.with_argparser(termdf_parser)
65 def do_terminate_df(self, opts):
66 """Terminate the specified DF. DANGEROUS! See TS 102 222 6.7.
67 This is a permanent, one-way operation on the card. There is no undo, you can not recover
68 a terminated DF. The only permitted command for a terminated DF is the DLETE FILE command."""
69 if not opts.force:
70 self._cmd.perror("Refusing to terminate the file, please read the help text.")
71 return
Harald Weltea6c0f882022-07-17 14:23:17 +020072 f = self._cmd.lchan.get_file_for_selectable(opts.NAME)
Harald Welte3c9b7842021-10-19 21:44:24 +020073 (data, sw) = self._cmd.card._scc.terminate_df(f.fid)
74
75 def complete_terminate_df(self, text, line, begidx, endidx) -> List[str]:
76 """Command Line tab completion for TERMINATE DF"""
Harald Weltea6c0f882022-07-17 14:23:17 +020077 index_dict = {1: self._cmd.lchan.selected_file.get_selectable_names()}
Harald Welte3c9b7842021-10-19 21:44:24 +020078 return self._cmd.index_based_complete(text, line, begidx, endidx, index_dict=index_dict)
79
80 @cmd2.with_argparser(termdf_parser)
81 def do_terminate_ef(self, opts):
82 """Terminate the specified EF. DANGEROUS! See TS 102 222 6.8.
83 This is a permanent, one-way operation on the card. There is no undo, you can not recover
84 a terminated EF. The only permitted command for a terminated EF is the DLETE FILE command."""
85 if not opts.force:
86 self._cmd.perror("Refusing to terminate the file, please read the help text.")
87 return
Harald Weltea6c0f882022-07-17 14:23:17 +020088 f = self._cmd.lchan.get_file_for_selectable(opts.NAME)
Harald Welte3c9b7842021-10-19 21:44:24 +020089 (data, sw) = self._cmd.card._scc.terminate_ef(f.fid)
90
91 def complete_terminate_ef(self, text, line, begidx, endidx) -> List[str]:
92 """Command Line tab completion for TERMINATE EF"""
Harald Weltea6c0f882022-07-17 14:23:17 +020093 index_dict = {1: self._cmd.lchan.selected_file.get_selectable_names()}
Harald Welte3c9b7842021-10-19 21:44:24 +020094 return self._cmd.index_based_complete(text, line, begidx, endidx, index_dict=index_dict)
95
96 tcard_parser = argparse.ArgumentParser()
97 tcard_parser.add_argument('--force-terminate-card', action='store_true',
98 help='I really want to permanently terminate the card. It will not be usable afterwards!')
99
100 @cmd2.with_argparser(tcard_parser)
101 def do_terminate_card_usage(self, opts):
102 """Terminate the Card. SUPER DANGEROUS! See TS 102 222 Section 6.9.
103 This will permanently brick the card and can NOT be recovered from!"""
104 if not opts.force_terminate_card:
105 self._cmd.perror("Refusing to permanently terminate the card, please read the help text.")
106 return
107 (data, sw) = self._cmd.card._scc.terminate_card_usage()
108
109 create_parser = argparse.ArgumentParser()
110 create_parser.add_argument('FILE_ID', type=str, help='File Identifier as 4-character hex string')
111 create_parser._action_groups.pop()
112 create_required = create_parser.add_argument_group('required arguments')
113 create_optional = create_parser.add_argument_group('optional arguments')
114 create_required.add_argument('--ef-arr-file-id', required=True, type=str, help='Referenced Security: File Identifier of EF.ARR')
115 create_required.add_argument('--ef-arr-record-nr', required=True, type=int, help='Referenced Security: Record Number within EF.ARR')
116 create_required.add_argument('--file-size', required=True, type=int, help='Size of file in octets')
117 create_required.add_argument('--structure', required=True, type=str, choices=['transparent', 'linear_fixed', 'ber_tlv'],
118 help='Structure of the to-be-created EF')
119 create_optional.add_argument('--short-file-id', type=str, help='Short File Identifier as 2-digit hex string')
120 create_optional.add_argument('--shareable', action='store_true', help='Should the file be shareable?')
121 create_optional.add_argument('--record-length', type=int, help='Length of each record in octets')
122
123 @cmd2.with_argparser(create_parser)
124 def do_create_ef(self, opts):
125 """Create a new EF below the currently selected DF. Requires related privileges."""
126 file_descriptor = {
127 'file_descriptor_byte': {
128 'shareable': opts.shareable,
129 'file_type': 'working_ef',
130 'structure': opts.structure,
131 }
132 }
133 if opts.structure == 'linear_fixed':
134 if not opts.record_length:
135 self._cmd.perror("you must specify the --record-length for linear fixed EF")
136 return
137 file_descriptor['record_len'] = opts.record_length
Christian Amsüsse17e2772022-04-19 10:29:09 +0200138 file_descriptor['num_of_rec'] = opts.file_size // opts.record_length
139 if file_descriptor['num_of_rec'] * file_descriptor['record_len'] != opts.file_size:
140 raise ValueError("File size not evenly divisible by record length")
Harald Welte3c9b7842021-10-19 21:44:24 +0200141 elif opts.structure == 'ber_tlv':
142 self._cmd.perror("BER-TLV creation not yet fully supported, sorry")
143 return
144 ies = [FileDescriptor(decoded=file_descriptor), FileIdentifier(decoded=opts.FILE_ID),
145 LifeCycleStatusInteger(decoded='operational_activated'),
146 SecurityAttribReferenced(decoded={'ef_arr_file_id': opts.ef_arr_file_id,
147 'ef_arr_record_nr': opts.ef_arr_record_nr }),
148 FileSize(decoded=opts.file_size),
149 ShortFileIdentifier(decoded=opts.short_file_id),
150 ]
151 fcp = FcpTemplate(children=ies)
152 (data, sw) = self._cmd.card._scc.create_file(b2h(fcp.to_tlv()))
153 # the newly-created file is automatically selected but our runtime state knows nothing of it
Harald Weltea6c0f882022-07-17 14:23:17 +0200154 self._cmd.lchan.select_file(self._cmd.lchan.selected_file)
Harald Welte3c9b7842021-10-19 21:44:24 +0200155
156 createdf_parser = argparse.ArgumentParser()
157 createdf_parser.add_argument('FILE_ID', type=str, help='File Identifier as 4-character hex string')
158 createdf_parser._action_groups.pop()
159 createdf_required = createdf_parser.add_argument_group('required arguments')
160 createdf_optional = createdf_parser.add_argument_group('optional arguments')
161 createdf_sja_optional = createdf_parser.add_argument_group('sysmoISIM-SJA optional arguments')
162 createdf_required.add_argument('--ef-arr-file-id', required=True, type=str, help='Referenced Security: File Identifier of EF.ARR')
163 createdf_required.add_argument('--ef-arr-record-nr', required=True, type=int, help='Referenced Security: Record Number within EF.ARR')
164 createdf_optional.add_argument('--shareable', action='store_true', help='Should the file be shareable?')
165 createdf_optional.add_argument('--aid', type=str, help='Application ID (creates an ADF, instead of a DF)')
166 # mandatory by spec, but ignored by several OS, so don't force the user
167 createdf_optional.add_argument('--total-file-size', type=int, help='Physical memory allocated for DF/ADi in octets')
168 createdf_sja_optional.add_argument('--permit-rfm-create', action='store_true')
169 createdf_sja_optional.add_argument('--permit-rfm-delete-terminate', action='store_true')
170 createdf_sja_optional.add_argument('--permit-other-applet-create', action='store_true')
171 createdf_sja_optional.add_argument('--permit-other-applet-delete-terminate', action='store_true')
172
173 @cmd2.with_argparser(createdf_parser)
174 def do_create_df(self, opts):
175 """Create a new DF below the currently selected DF. Requires related privileges."""
176 file_descriptor = {
177 'file_descriptor_byte': {
178 'shareable': opts.shareable,
179 'file_type': 'df',
180 'structure': 'no_info_given',
181 }
182 }
183 ies = []
184 ies.append(FileDescriptor(decoded=file_descriptor))
185 ies.append(FileIdentifier(decoded=opts.FILE_ID))
186 if opts.aid:
187 ies.append(DfName(decoded=opts.aid))
188 ies.append(LifeCycleStatusInteger(decoded='operational_activated'))
189 ies.append(SecurityAttribReferenced(decoded={'ef_arr_file_id': opts.ef_arr_file_id,
190 'ef_arr_record_nr': opts.ef_arr_record_nr }))
191 if opts.total_file_size:
192 ies.append(TotalFileSize(decoded=opts.total_file_size))
193 # TODO: Spec states PIN Status Template DO is mandatory
194 if opts.permit_rfm_create or opts.permit_rfm_delete_terminate or opts.permit_other_applet_create or opts.permit_other_applet_delete_terminate:
195 toolkit_ac = {
196 'rfm_create': opts.permit_rfm_create,
197 'rfm_delete_terminate': opts.permit_rfm_delete_terminate,
198 'other_applet_create': opts.permit_other_applet_create,
199 'other_applet_delete_terminate': opts.permit_other_applet_delete_terminate,
200 }
201 ies.append(ProprietaryInformation(children=[ToolkitAccessConditions(decoded=toolkit_ac)]))
202 fcp = FcpTemplate(children=ies)
203 (data, sw) = self._cmd.card._scc.create_file(b2h(fcp.to_tlv()))
204 # the newly-created file is automatically selected but our runtime state knows nothing of it
Harald Weltea6c0f882022-07-17 14:23:17 +0200205 self._cmd.lchan.select_file(self._cmd.lchan.selected_file)
Harald Welte0707b802023-03-07 11:43:37 +0100206
207 resize_ef_parser = argparse.ArgumentParser()
208 resize_ef_parser.add_argument('NAME', type=str, help='Name or FID of file to be resized')
209 resize_ef_parser._action_groups.pop()
210 resize_ef_required = resize_ef_parser.add_argument_group('required arguments')
211 resize_ef_required.add_argument('--file-size', required=True, type=int, help='Size of file in octets')
212
213 @cmd2.with_argparser(resize_ef_parser)
214 def do_resize_ef(self, opts):
215 """Resize an existing EF below the currently selected DF. Requires related privileges."""
216 f = self._cmd.lchan.get_file_for_selectable(opts.NAME)
217 ies = [FileIdentifier(decoded=f.fid),
218 FileSize(decoded=opts.file_size)]
219 fcp = FcpTemplate(children=ies)
220 (data, sw) = self._cmd.card._scc.resize_file(b2h(fcp.to_tlv()))
221 # the resized file is automatically selected but our runtime state knows nothing of it
222 self._cmd.lchan.select_file(self._cmd.lchan.selected_file)
223
224 def complete_resize_ef(self, text, line, begidx, endidx) -> List[str]:
225 """Command Line tab completion for RESIZE EF"""
226 index_dict = {1: self._cmd.lchan.selected_file.get_selectable_names()}
227 return self._cmd.index_based_complete(text, line, begidx, endidx, index_dict=index_dict)