laforge has submitted this change. ( https://gerrit.osmocom.org/c/pysim/+/37454?usp=email )
Change subject: global_platform: LOAD and INSTALL [for load] support ......................................................................
global_platform: LOAD and INSTALL [for load] support
In this patch we add the commands "install_for_load" and "load".
Depends: pyosmocom.git I86df064fa41db85923eeb0d83cc399504fdd4488 Change-Id: I924aaeecbb3a72bdb65eefbff6135e4e9570579e Related: OS#6679 --- M docs/shell.rst M pySim/global_platform/__init__.py M requirements.txt M setup.py 4 files changed, 80 insertions(+), 4 deletions(-)
Approvals: laforge: Looks good to me, approved Jenkins Builder: Verified
diff --git a/docs/shell.rst b/docs/shell.rst index 31689fd..8bb0a17 100644 --- a/docs/shell.rst +++ b/docs/shell.rst @@ -1047,6 +1047,12 @@ :module: pySim.global_platform :func: ADF_SD.AddlShellCommands.del_key_parser
+load +~~~~ +.. argparse:: + :module: pySim.global_platform + :func: ADF_SD.AddlShellCommands.load_parser + install_for_personalization ~~~~~~~~~~~~~~~~~~~~~~~~~~~ .. argparse:: @@ -1059,6 +1065,12 @@ :module: pySim.global_platform :func: ADF_SD.AddlShellCommands.inst_inst_parser
+install_for_load +~~~~~~~~~~~~~~~~ +.. argparse:: + :module: pySim.global_platform + :func: ADF_SD.AddlShellCommands.inst_load_parser + delete_card_content ~~~~~~~~~~~~~~~~~~~ .. argparse:: diff --git a/pySim/global_platform/__init__.py b/pySim/global_platform/__init__.py index f2bec81..ed4ee68 100644 --- a/pySim/global_platform/__init__.py +++ b/pySim/global_platform/__init__.py @@ -17,6 +17,7 @@ along with this program. If not, see http://www.gnu.org/licenses/. """
+import io from copy import deepcopy from typing import Optional, List, Dict, Tuple from construct import Optional as COptional @@ -32,6 +33,7 @@ from pySim.filesystem import * from pySim.profile import CardProfile from pySim.ota import SimFileAccessAndToolkitAppSpecParams +from pySim.javacard import CapFile
# GPCS Table 11-48 Load Parameter Tags class NonVolatileCodeMinMemoryReq(BER_TLV_IE, tag=0xC6): @@ -464,8 +466,7 @@
# Section 11.4.3.1 Table 11-36 + Section 11.1.2 class Privileges(BER_TLV_IE, tag=0xc5): - # we only support 3-byte encoding. Can't use StripTrailerAdapter as length==2 is not permitted. sigh. - _construct = FlagsEnum(Int24ub, + _construct = FlagsEnum(StripTrailerAdapter(GreedyBytes, 3, steps = [1, 3]), security_domain=0x800000, dap_verification=0x400000, delegated_management=0x200000, card_lock=0x100000, card_terminate=0x080000, card_reset=0x040000, cvm_management=0x020000, @@ -751,6 +752,31 @@ ifi_bytes = build_construct(InstallForInstallCD, decoded) self.install(p1, 0x00, b2h(ifi_bytes))
+ inst_load_parser = argparse.ArgumentParser() + inst_load_parser.add_argument('--load-file-aid', type=is_hexstr, required=True, + help='AID of the loded file') + inst_load_parser.add_argument('--security-domain-aid', type=is_hexstr, default='', + help='AID of the Security Domain into which the file shalle be added') + inst_load_parser.add_argument('--load-file-hash', type=is_hexstr, default='', + help='Load File Data Block Hash (GPC_SPE_034, section C.2)') + inst_load_parser.add_argument('--load-parameters', type=is_hexstr, default='', + help='Load Parameters (GPC_SPE_034, section 11.5.2.3.7, table 11-49)') + inst_load_parser.add_argument('--load-token', type=is_hexstr, default='', + help='Load Token (GPC_SPE_034, section C.4.1)') + + @cmd2.with_argparser(inst_load_parser) + def do_install_for_load(self, opts): + """Perform GlobalPlatform INSTALL [for load] command in order to prepare to load an application.""" + if opts.load_token != '' and opts.load_file_hash == '': + raise ValueError('Load File Data Block Hash is mandatory if a Load Token is present') + InstallForLoadCD = Struct('load_file_aid'/HexAdapter(Prefixed(Int8ub, GreedyBytes)), + 'security_domain_aid'/HexAdapter(Prefixed(Int8ub, GreedyBytes)), + 'load_file_hash'/HexAdapter(Prefixed(Int8ub, GreedyBytes)), + 'load_parameters'/HexAdapter(Prefixed(Int8ub, GreedyBytes)), + 'load_token'/HexAdapter(Prefixed(Int8ub, GreedyBytes))) + ifl_bytes = build_construct(InstallForLoadCD, vars(opts)) + self.install(0x02, 0x00, b2h(ifl_bytes)) + def install(self, p1:int, p2:int, data:Hexstr) -> ResTuple: cmd_hex = "80E6%02x%02x%02x%s00" % (p1, p2, len(data)//2, data) return self._cmd.lchan.scc.send_apdu_checksw(cmd_hex) @@ -794,6 +820,44 @@ cmd_hex = "80E4%02x%02x%02x%s00" % (p1, p2, len(data)//2, data) return self._cmd.lchan.scc.send_apdu_checksw(cmd_hex)
+ load_parser = argparse.ArgumentParser() + load_parser_from_grp = load_parser.add_mutually_exclusive_group(required=True) + load_parser_from_grp.add_argument('--from-hex', type=is_hexstr, help='load from hex string') + load_parser_from_grp.add_argument('--from-file', type=argparse.FileType('rb', 0), help='load from binary file') + load_parser_from_grp.add_argument('--from-cap-file', type=argparse.FileType('rb', 0), help='load from JAVA-card CAP file') + + @cmd2.with_argparser(load_parser) + def do_load(self, opts): + """Perform a GlobalPlatform LOAD command. (We currently only support loading without DAP and + without ciphering.)""" + if opts.from_hex is not None: + self.load(h2b(opts.from_hex)) + elif opts.from_file is not None: + self.load(opts.from_file.read()) + elif opts.from_cap_file is not None: + cap = CapFile(opts.from_cap_file) + self.load(cap.get_loadfile()) + else: + raise ValueError('load source not specified!') + + def load(self, contents:bytes, chunk_len:int = 240): + # TODO:tune chunk_len based on the overhead of the used SCP? + # build TLV according to GPC_SPE_034 section 11.6.2.3 / Table 11-58 for unencrypted case + remainder = b'\xC4' + bertlv_encode_len(len(contents)) + contents + # transfer this in vaious chunks to the card + total_size = len(remainder) + block_nr = 0 + while len(remainder): + block = remainder[:chunk_len] + remainder = remainder[chunk_len:] + # build LOAD command APDU according to GPC_SPE_034 section 11.6.2 / Table 11-56 + p1 = 0x00 if len(remainder) else 0x80 + p2 = block_nr % 256 + block_nr += 1 + cmd_hex = "80E8%02x%02x%02x%s00" % (p1, p2, len(block), b2h(block)) + _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)) + 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('--host-challenge', type=is_hexstr, diff --git a/requirements.txt b/requirements.txt index 58473ca..16fa886 100644 --- a/requirements.txt +++ b/requirements.txt @@ -5,7 +5,7 @@ jsonpath-ng construct>=2.10.70 bidict -pyosmocom>=0.0.6 +pyosmocom>=0.0.8 pyyaml>=5.1 termcolor colorlog diff --git a/setup.py b/setup.py index e353f8a..f90e07b 100644 --- a/setup.py +++ b/setup.py @@ -24,7 +24,7 @@ "jsonpath-ng", "construct >= 2.10.70", "bidict", - "pyosmocom >= 0.0.6", + "pyosmocom >= 0.0.8", "pyyaml >= 5.1", "termcolor", "colorlog",