mirror of
https://gitea.osmocom.org/sim-card/pysim.git
synced 2026-03-16 18:38:32 +03:00
global_platform: add new command "install_cap"
Installing JAVA-card applets from a CAP file is a multi step process, which is difficult when done manually. Fortunately it is easy to automate the process, so let's add a dedicated command for that. Change-Id: I6cbd37f0fad5579b20e83c27349bd5acc129e6d0 Related: OS#6679
This commit is contained in:
103
docs/cap-tutorial.rst
Normal file
103
docs/cap-tutorial.rst
Normal file
@@ -0,0 +1,103 @@
|
|||||||
|
|
||||||
|
Guide: Installing JAVA-card applets
|
||||||
|
===================================
|
||||||
|
|
||||||
|
Almost all modern-day UICC cards have some form of JAVA-card / Sim-Toolkit support, which allows the installation
|
||||||
|
of customer specific JAVA-card applets. The installation of JAVA-card applets is usually done via the standardized
|
||||||
|
GlobalPlatform (GPC_SPE_034) ISD (Issuer Security Domain) application interface during the card provisioning process.
|
||||||
|
(it is also possible to load JAVA-card applets in field via OTA-SMS, but that is beyond the scope of this guide). In
|
||||||
|
this guide we will go through the individual steps that are required to load JAVA-card applet onto an UICC card.
|
||||||
|
|
||||||
|
|
||||||
|
Preparation
|
||||||
|
~~~~~~~~~~~
|
||||||
|
|
||||||
|
In this example we will install the CAP file HelloSTK_09122024.cap [1] on an sysmoISIM-SJA2 card. Since the interface
|
||||||
|
is standardized, the exact card model does not matter.
|
||||||
|
|
||||||
|
The example applet makes use of the STK (Sim-Toolkit), so we must supply STK installation parameters. Those
|
||||||
|
parameters are supplied in the form of a hexstring and should be provided by the applet manufacturer. The available
|
||||||
|
parameters and their exact encoding is specified in ETSI TS 102 226, section 8.2.1.3.2.1. The installation of
|
||||||
|
HelloSTK_09122024.cap [1], will require the following STK installation parameters: "010001001505000000000000000000000000"
|
||||||
|
|
||||||
|
During the installation, we also have to set a memory quota for the volatile and for the non volatile card memory.
|
||||||
|
Those values also should be provided by the applet manufacturer. In this example, we will allow 255 bytes of volatile
|
||||||
|
memory and 255 bytes of non volatile memory to be consumed by the applet.
|
||||||
|
|
||||||
|
To install JAVA-card applets, one must be in the possession of the key material belonging to the card. The keys are
|
||||||
|
usually provided by the card manufacturer. The following example will use the following keyset:
|
||||||
|
|
||||||
|
+---------+----------------------------------+
|
||||||
|
| Keyname | Keyvalue |
|
||||||
|
+=========+==================================+
|
||||||
|
| DEK/KIK | 5524F4BECFE96FB63FC29D6BAAC6058B |
|
||||||
|
+---------+----------------------------------+
|
||||||
|
| ENC/KIC | 542C37A6043679F2F9F71116418B1CD5 |
|
||||||
|
+---------+----------------------------------+
|
||||||
|
| MAC/KID | 34F11BAC8E5390B57F4E601372339E3C |
|
||||||
|
+---------+----------------------------------+
|
||||||
|
|
||||||
|
[1] https://osmocom.org/projects/cellular-infrastructure/wiki/HelloSTK
|
||||||
|
|
||||||
|
|
||||||
|
Applet Installation
|
||||||
|
~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
To prepare the installation, a secure channel to the ISD must be established first:
|
||||||
|
|
||||||
|
::
|
||||||
|
|
||||||
|
pySIM-shell (00:MF)> select ADF.ISD
|
||||||
|
{
|
||||||
|
"application_id": "a000000003000000",
|
||||||
|
"proprietary_data": {
|
||||||
|
"maximum_length_of_data_field_in_command_message": 255
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pySIM-shell (00:MF/ADF.ISD)> establish_scp02 --key-dek 5524F4BECFE96FB63FC29D6BAAC6058B --key-enc 542C37A6043679F2F9F71116418B1CD5 --key-mac 34F11BAC8E5390B57F4E601372339E3C --security-level 1
|
||||||
|
Successfully established a SCP02[01] secure channel
|
||||||
|
|
||||||
|
.. warning:: In case you get an "EXCEPTION of type 'ValueError' occurred with message: card cryptogram doesn't match" error message, it is very likely that there is a problem with the key material. The card may lock the ISD access after a certain amount of failed tries. Carefully check the key material any try again.
|
||||||
|
|
||||||
|
|
||||||
|
When the secure channel is established, we are ready to install the applet. The installation normally is a multi step
|
||||||
|
procedure, where the loading of an executable load file is announced first, then loaded and then installed in a final
|
||||||
|
step. The pySim-shell command ``install_cap`` automatically takes care of those three steps.
|
||||||
|
|
||||||
|
::
|
||||||
|
|
||||||
|
pySIM-shell (SCP02[01]:00:MF/ADF.ISD)> install_cap /home/user/HelloSTK_09122024.cap --install-parameters-non-volatile-memory-quota 255 --install-parameters-volatile-memory-quota 255 --install-parameters-stk 010001001505000000000000000000000000
|
||||||
|
loading cap file: /home/user/HelloSTK_09122024.cap ...
|
||||||
|
parameters:
|
||||||
|
security-domain-aid: a000000003000000
|
||||||
|
load-file: 569 bytes
|
||||||
|
load-file-aid: d07002ca44
|
||||||
|
module-aid: d07002ca44900101
|
||||||
|
application-aid: d07002ca44900101
|
||||||
|
install-parameters: c900ef1cc80200ffc70200ffca12010001001505000000000000000000000000
|
||||||
|
step #1: install for load...
|
||||||
|
step #2: load...
|
||||||
|
Loaded a total of 573 bytes in 3 blocks. Don't forget install_for_install (and make selectable) now!
|
||||||
|
step #3: install_for_install (and make selectable)...
|
||||||
|
done.
|
||||||
|
|
||||||
|
The applet is now installed on the card. We can now quit pySim-shell and remove the card from the reader and test the
|
||||||
|
applet in a mobile phone. There should be a new STK application with one menu entry shown, that will greet the user
|
||||||
|
when pressed.
|
||||||
|
|
||||||
|
|
||||||
|
Applet Removal
|
||||||
|
~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
To remove the applet, we must establish a secure channel to the ISD (see above). Then we can delete the applet using the
|
||||||
|
``delete_card_content`` command.
|
||||||
|
|
||||||
|
::
|
||||||
|
|
||||||
|
pySIM-shell (SCP02[01]:00:MF/ADF.ISD)> delete_card_content D07002CA44 --delete-related-objects
|
||||||
|
|
||||||
|
The parameter "D07002CA44" is the load-file-AID of the applet. The load-file-AID is encoded in the .cap file and also
|
||||||
|
displayed during the installation process. It is also important to note that when the applet is installed, it cannot
|
||||||
|
be installed (under the same AID) again until it is removed.
|
||||||
|
|
||||||
|
|
||||||
@@ -67,6 +67,7 @@ Usage Examples
|
|||||||
:caption: Tutorials for pySIM-shell:
|
:caption: Tutorials for pySIM-shell:
|
||||||
|
|
||||||
suci-tutorial
|
suci-tutorial
|
||||||
|
cap-tutorial
|
||||||
|
|
||||||
|
|
||||||
Advanced Topics
|
Advanced Topics
|
||||||
@@ -1053,6 +1054,12 @@ load
|
|||||||
:module: pySim.global_platform
|
:module: pySim.global_platform
|
||||||
:func: ADF_SD.AddlShellCommands.load_parser
|
:func: ADF_SD.AddlShellCommands.load_parser
|
||||||
|
|
||||||
|
install_cap
|
||||||
|
~~~~~~~~~~~
|
||||||
|
.. argparse::
|
||||||
|
:module: pySim.global_platform
|
||||||
|
:func: ADF_SD.AddlShellCommands.install_cap_parser
|
||||||
|
|
||||||
install_for_personalization
|
install_for_personalization
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
.. argparse::
|
.. argparse::
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ from osmocom.construct import *
|
|||||||
from pySim.utils import ResTuple
|
from pySim.utils import ResTuple
|
||||||
from pySim.card_key_provider import card_key_provider_get_field
|
from pySim.card_key_provider import card_key_provider_get_field
|
||||||
from pySim.global_platform.scp import SCP02, SCP03
|
from pySim.global_platform.scp import SCP02, SCP03
|
||||||
|
from pySim.global_platform.install_param import gen_install_parameters
|
||||||
from pySim.filesystem import *
|
from pySim.filesystem import *
|
||||||
from pySim.profile import CardProfile
|
from pySim.profile import CardProfile
|
||||||
from pySim.ota import SimFileAccessAndToolkitAppSpecParams
|
from pySim.ota import SimFileAccessAndToolkitAppSpecParams
|
||||||
@@ -858,6 +859,58 @@ class ADF_SD(CardADF):
|
|||||||
_rsp_hex, _sw = self._cmd.lchan.scc.send_apdu_checksw(cmd_hex)
|
_rsp_hex, _sw = self._cmd.lchan.scc.send_apdu_checksw(cmd_hex)
|
||||||
self._cmd.poutput("Loaded a total of %u bytes in %u blocks. Don't forget install_for_install (and make selectable) now!" % (total_size, block_nr))
|
self._cmd.poutput("Loaded a total of %u bytes in %u blocks. Don't forget install_for_install (and make selectable) now!" % (total_size, block_nr))
|
||||||
|
|
||||||
|
install_cap_parser = argparse.ArgumentParser()
|
||||||
|
install_cap_parser.add_argument('cap_file', type=str, metavar='FILE',
|
||||||
|
help='JAVA-CARD CAP file to install')
|
||||||
|
install_cap_parser_inst_prm_g = install_cap_parser.add_mutually_exclusive_group()
|
||||||
|
install_cap_parser_inst_prm_g.add_argument('--install-parameters', type=is_hexstr, default=None,
|
||||||
|
help='install Parameters (GPC_SPE_034, section 11.5.2.3.7, table 11-49)')
|
||||||
|
install_cap_parser_inst_prm_g_grp = install_cap_parser_inst_prm_g.add_argument_group()
|
||||||
|
install_cap_parser_inst_prm_g_grp.add_argument('--install-parameters-volatile-memory-quota',
|
||||||
|
type=int, default=None,
|
||||||
|
help='volatile memory quota (GPC_SPE_034, section 11.5.2.3.7, table 11-49)')
|
||||||
|
install_cap_parser_inst_prm_g_grp.add_argument('--install-parameters-non-volatile-memory-quota',
|
||||||
|
type=int, default=None,
|
||||||
|
help='non volatile memory quota (GPC_SPE_034, section 11.5.2.3.7, table 11-49)')
|
||||||
|
install_cap_parser_inst_prm_g_grp.add_argument('--install-parameters-stk',
|
||||||
|
type=is_hexstr, default=None,
|
||||||
|
help='Load Parameters (ETSI TS 102 226, section 8.2.1.3.2.1)')
|
||||||
|
|
||||||
|
@cmd2.with_argparser(install_cap_parser)
|
||||||
|
def do_install_cap(self, opts):
|
||||||
|
"""Perform a .cap file installation using GlobalPlatform LOAD and INSTALL commands."""
|
||||||
|
|
||||||
|
self._cmd.poutput("loading cap file: %s ..." % opts.cap_file)
|
||||||
|
cap = CapFile(opts.cap_file)
|
||||||
|
|
||||||
|
security_domain_aid = self._cmd.lchan.selected_file.aid
|
||||||
|
load_file = cap.get_loadfile()
|
||||||
|
load_file_aid = cap.get_loadfile_aid()
|
||||||
|
module_aid = cap.get_applet_aid()
|
||||||
|
application_aid = module_aid
|
||||||
|
if opts.install_parameters:
|
||||||
|
install_parameters = opts.install_parameters;
|
||||||
|
else:
|
||||||
|
install_parameters = gen_install_parameters(opts.install_parameters_non_volatile_memory_quota,
|
||||||
|
opts.install_parameters_volatile_memory_quota,
|
||||||
|
opts.install_parameters_stk)
|
||||||
|
self._cmd.poutput("parameters:")
|
||||||
|
self._cmd.poutput(" security-domain-aid: %s" % security_domain_aid)
|
||||||
|
self._cmd.poutput(" load-file: %u bytes" % len(load_file))
|
||||||
|
self._cmd.poutput(" load-file-aid: %s" % load_file_aid)
|
||||||
|
self._cmd.poutput(" module-aid: %s" % module_aid)
|
||||||
|
self._cmd.poutput(" application-aid: %s" % application_aid)
|
||||||
|
self._cmd.poutput(" install-parameters: %s" % install_parameters)
|
||||||
|
|
||||||
|
self._cmd.poutput("step #1: install for load...")
|
||||||
|
self.do_install_for_load("--load-file-aid %s --security-domain-aid %s" % (load_file_aid, security_domain_aid))
|
||||||
|
self._cmd.poutput("step #2: load...")
|
||||||
|
self.load(load_file)
|
||||||
|
self._cmd.poutput("step #3: install_for_install (and make selectable)...")
|
||||||
|
self.do_install_for_install("--load-file-aid %s --module-aid %s --application-aid %s --install-parameters %s --make-selectable" %
|
||||||
|
(load_file_aid, module_aid, application_aid, install_parameters))
|
||||||
|
self._cmd.poutput("done.")
|
||||||
|
|
||||||
est_scp02_parser = argparse.ArgumentParser()
|
est_scp02_parser = argparse.ArgumentParser()
|
||||||
est_scp02_parser.add_argument('--key-ver', type=auto_uint8, default=0, help='Key Version Number (KVN)')
|
est_scp02_parser.add_argument('--key-ver', type=auto_uint8, default=0, help='Key Version Number (KVN)')
|
||||||
est_scp02_parser.add_argument('--host-challenge', type=is_hexstr,
|
est_scp02_parser.add_argument('--host-challenge', type=is_hexstr,
|
||||||
|
|||||||
72
pySim/global_platform/install_param.py
Normal file
72
pySim/global_platform/install_param.py
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
# GlobalPlatform install parameter generator
|
||||||
|
#
|
||||||
|
# (C) 2024 by Sysmocom s.f.m.c. GmbH
|
||||||
|
# All Rights Reserved
|
||||||
|
#
|
||||||
|
# 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 osmocom.construct import *
|
||||||
|
from osmocom.utils import *
|
||||||
|
from osmocom.tlv import *
|
||||||
|
|
||||||
|
class AppSpecificParams(BER_TLV_IE, tag=0xC9):
|
||||||
|
# GPD_SPE_013, table 11-49
|
||||||
|
_construct = HexAdapter(GreedyBytes)
|
||||||
|
|
||||||
|
class VolatileMemoryQuota(BER_TLV_IE, tag=0xC7):
|
||||||
|
# GPD_SPE_013, table 11-49
|
||||||
|
_construct = StripHeaderAdapter(GreedyBytes, 4, steps = [2,4])
|
||||||
|
|
||||||
|
class NonVolatileMemoryQuota(BER_TLV_IE, tag=0xC8):
|
||||||
|
# GPD_SPE_013, table 11-49
|
||||||
|
_construct = StripHeaderAdapter(GreedyBytes, 4, steps = [2,4])
|
||||||
|
|
||||||
|
class StkParameter(BER_TLV_IE, tag=0xCA):
|
||||||
|
# GPD_SPE_013, table 11-49
|
||||||
|
# ETSI TS 102 226, section 8.2.1.3.2.1
|
||||||
|
_construct = HexAdapter(GreedyBytes)
|
||||||
|
|
||||||
|
class SystemSpecificParams(BER_TLV_IE, tag=0xEF, nested=[VolatileMemoryQuota, NonVolatileMemoryQuota, StkParameter]):
|
||||||
|
# GPD_SPE_013 v1.1 Table 6-5
|
||||||
|
pass
|
||||||
|
|
||||||
|
class InstallParams(TLV_IE_Collection, nested=[AppSpecificParams, SystemSpecificParams]):
|
||||||
|
# GPD_SPE_013, table 11-49
|
||||||
|
pass
|
||||||
|
|
||||||
|
def gen_install_parameters(non_volatile_memory_quota:int, volatile_memory_quota:int, stk_parameter:str):
|
||||||
|
|
||||||
|
# GPD_SPE_013, table 11-49
|
||||||
|
|
||||||
|
#Mandatory
|
||||||
|
install_params = InstallParams()
|
||||||
|
install_params_dict = [{'app_specific_params': None}]
|
||||||
|
|
||||||
|
#Conditional
|
||||||
|
if non_volatile_memory_quota and volatile_memory_quota and stk_parameter:
|
||||||
|
system_specific_params = []
|
||||||
|
#Optional
|
||||||
|
if non_volatile_memory_quota:
|
||||||
|
system_specific_params += [{'non_volatile_memory_quota': non_volatile_memory_quota}]
|
||||||
|
#Optional
|
||||||
|
if volatile_memory_quota:
|
||||||
|
system_specific_params += [{'volatile_memory_quota': volatile_memory_quota}]
|
||||||
|
#Optional
|
||||||
|
if stk_parameter:
|
||||||
|
system_specific_params += [{'stk_parameter': stk_parameter}]
|
||||||
|
install_params_dict += [{'system_specific_params': system_specific_params}]
|
||||||
|
|
||||||
|
install_params.from_dict(install_params_dict)
|
||||||
|
return b2h(install_params.to_bytes())
|
||||||
@@ -21,6 +21,7 @@ from osmocom.utils import b2h, h2b
|
|||||||
|
|
||||||
from pySim.global_platform import *
|
from pySim.global_platform import *
|
||||||
from pySim.global_platform.scp import *
|
from pySim.global_platform.scp import *
|
||||||
|
from pySim.global_platform.install_param import gen_install_parameters
|
||||||
|
|
||||||
KIC = h2b('100102030405060708090a0b0c0d0e0f') # enc
|
KIC = h2b('100102030405060708090a0b0c0d0e0f') # enc
|
||||||
KID = h2b('101102030405060708090a0b0c0d0e0f') # MAC
|
KID = h2b('101102030405060708090a0b0c0d0e0f') # MAC
|
||||||
@@ -289,5 +290,13 @@ class SCP03_KCV_Test(unittest.TestCase):
|
|||||||
self.assertEqual(compute_kcv('aes', KEYSET_AES128.dek), h2b('840DE5'))
|
self.assertEqual(compute_kcv('aes', KEYSET_AES128.dek), h2b('840DE5'))
|
||||||
|
|
||||||
|
|
||||||
|
class Install_param_Test(unittest.TestCase):
|
||||||
|
def test_gen_install_parameters(self):
|
||||||
|
load_parameters = gen_install_parameters(256, 256, '010001001505000000000000000000000000')
|
||||||
|
self.assertEqual(load_parameters, 'c900ef1cc8020100c7020100ca12010001001505000000000000000000000000')
|
||||||
|
|
||||||
|
load_parameters = gen_install_parameters(None, None, '')
|
||||||
|
self.assertEqual(load_parameters, 'c900')
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
|||||||
Reference in New Issue
Block a user