laforge has uploaded this change for review. ( https://gerrit.osmocom.org/c/pysim/+/37454?usp=email )
Change subject: WIP: global_platform: LOAD and INSTALL [for load] support ......................................................................
WIP: global_platform: LOAD and INSTALL [for load] support
Change-Id: I924aaeecbb3a72bdb65eefbff6135e4e9570579e --- M pySim/global_platform/__init__.py 1 file changed, 83 insertions(+), 0 deletions(-)
git pull ssh://gerrit.osmocom.org:29418/pysim refs/changes/54/37454/1
diff --git a/pySim/global_platform/__init__.py b/pySim/global_platform/__init__.py index 0be316b..ab454a5 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 @@ -435,6 +436,23 @@ ExecutableModuleAID, AssociatedSecurityDomainAID]): pass
+ +# Section 11.6.2.3 / Table 11-58 +class SecurityDomainAid(BER_TLV_IE, tag=0x4f): + _construct = GreedyBytes +class LoadFileDataBlockSignature(BER_TLV_IE, tag=0xc3): + _construct = GreedyBytes +class DapBlock(BER_TLV_IE, tag=0xe2, nested=[SecurityDomainAid, LoadFileDataBlockSignature]): + pass +class LoadFileDataBlock(BER_TLV_IE, tag=0xc4): + _construct = GreedyBytes +class Icv(BER_TLV_IE, tag=0xd3): + _construct = GreedyBytes +class CipheredLoadFileDataBlock(BER_TLV_IE, tag=0xd4): + _construct = GreedyBytes +class LoadFile(TLV_IE_Collection, nested=[DapBlock, LoadFileDataBlock, Icv, CipheredLoadFileDataBlock]): + pass + # Application Dedicated File of a Security Domain class ADF_SD(CardADF): StoreData = BitStruct('last_block'/Flag, @@ -676,6 +694,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') + inst_inst_parser.add_argument('--load-parameters', type=is_hexstr, default='', + help='Load Token (Section GPCS C.4.1)') + inst_inst_parser.add_argument('--load-token', type=is_hexstr, default='', + help='Load Token (Section GPCS C.4.1)') + + @cmd2.with_argparser(inst_load_parser) + def do_install_for_load(self, opts): + """Perform GlobalPlatform INSTALL [for load] command.""" + 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%s" % (p1, p2, len(data)//2, data) return self._cmd.lchan.scc.send_apdu_checksw(cmd_hex) @@ -719,6 +762,37 @@ cmd_hex = "80E4%02x%02x%02x%s" % (p1, p2, len(data)//2, data) return self._cmd.lchan.scc.send_apdu_checksw(cmd_hex)
+ load_parser = argparse.ArgumentParser() + # we make this a required --optional argument now, so we can later have other sources for load data + load_parser.add_argument('--from-file', required=True) + + @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.""" + with open(opts.from_file, 'rb') as f: + self.load(f) + + def load(self, stream: io.RawIOBase, chunk_len:int = 240): + # we might want to tune chunk_len based on the overhead of the used SCP? + contents = stream.readall() + # build TLV according to 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 11.6.2 / Table 11-56 + p1 = 0x80 if len(remainder) else 0x00 + p2 = block_nr % 256 + block_nr += 1 + cmd_hex = "80E8%02x%02x%02x%s" % (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_load now!" % (total_size, block_nr)) + + est_scp02_parser = argparse.ArgumentParser() est_scp02_parser.add_argument('--key-ver', type=auto_uint8, required=True, help='Key Version Number (KVN)') est_scp02_parser.add_argument('--host-challenge', type=is_hexstr,