Hoernchen has uploaded this change for review. ( https://gerrit.osmocom.org/c/pysim/+/43542?usp=email )
Change subject: smpp-ota-tool: add --format compact,expanded for TS 102 226 5.2 ......................................................................
smpp-ota-tool: add --format compact,expanded for TS 102 226 5.2
Add --format expanded flag to send C-APDUs in the expanded remote application data format. Each --apdu becomes its own C-APDU TLV, and the tool logs the full per-command R-APDU list decoded from the Response Scripting template and warns if the card reports a truncated response.
Default stays 'compact', unchanged.
Change-Id: Idad90756f85bb7e54ef720f6067bb5d6dcff0c42 --- M contrib/smpp-ota-tool.py M docs/smpp-ota-tool.rst 2 files changed, 64 insertions(+), 7 deletions(-)
git pull ssh://gerrit.osmocom.org:29418/pysim refs/changes/42/43542/1
diff --git a/contrib/smpp-ota-tool.py b/contrib/smpp-ota-tool.py index 28c13bf..3698001 100755 --- a/contrib/smpp-ota-tool.py +++ b/contrib/smpp-ota-tool.py @@ -70,6 +70,8 @@ option_parser.add_argument('--src-addr', default='12', type=str, help='SMS source address (MSISDN)') option_parser.add_argument('--dest-addr', default='23', type=str, help='SMS destination address (MSISDN)') option_parser.add_argument('--timeout', default=10, type=int, help='Maximum response waiting time') +option_parser.add_argument('--format', choices=['compact', 'expanded'], default='compact', + help="Remote Application data format: 'compact' or 'expanded'") option_parser.add_argument('-a', '--apdu', action='append', required=True, type=is_hexstr, help='C-APDU to send')
class SmppHandler: @@ -77,7 +79,8 @@
def __init__(self, host: str, port: int, system_id: str, password: str, - ota_keyset: OtaKeyset, spi: dict, tar: bytes): + ota_keyset: OtaKeyset, spi: dict, tar: bytes, + remote_format: str = 'compact'): """ Initialize connection to SMPP server and set static OTA SMS-TPDU ciphering parameters Args: @@ -88,6 +91,7 @@ ota_keyset: OTA keyset to be used for SMS-TPDU ciphering spi: Security Parameter Indicator (SPI) to be used for SMS-TPDU ciphering tar: Toolkit Application Reference (TAR) of the targeted card application + remote_format: Remote Application data format ('compact' or 'expanded', TS 102 226) """
# Create and connect SMPP client @@ -103,6 +107,7 @@ self.ota_keyset = ota_keyset self.tar = tar self.spi = spi + self.remote_format = remote_format
def __del__(self): if self.client: @@ -113,14 +118,16 @@ if pdu.short_message: logger.info("SMS-TPDU received: %s", b2h(pdu.short_message)) try: - dec = self.ota_dialect.decode_resp(self.ota_keyset, self.spi, pdu.short_message) + dec = self.ota_dialect.decode_resp(self.ota_keyset, self.spi, pdu.short_message, + remote_format=self.remote_format) except ValueError: # Retry to decoding with ciphering disabled (in case the card has problems to decode the SMS-TDPU # we have sent, the response will contain an unencrypted error message) spi = self.spi.copy() spi['por_shall_be_ciphered'] = False spi['por_rc_cc_ds'] = 'no_rc_cc_ds' - dec = self.ota_dialect.decode_resp(self.ota_keyset, spi, pdu.short_message) + dec = self.ota_dialect.decode_resp(self.ota_keyset, spi, pdu.short_message, + remote_format=self.remote_format) logger.info("SMS-TPDU decoded: %s", dec) self.response = dec return None @@ -183,10 +190,14 @@ tuple containing the last response data and the last status word as byte strings """
- logger.info("C-APDU sending: %s...", b2h(apdu)) + if isinstance(apdu, (list, tuple)): + logger.info("C-APDU(s) sending: %s...", [b2h(a) for a in apdu]) + else: + logger.info("C-APDU sending: %s...", b2h(apdu))
# translate to Secured OTA RFM - secured = self.ota_dialect.encode_cmd(self.ota_keyset, self.tar, self.spi, apdu=apdu) + secured = self.ota_dialect.encode_cmd(self.ota_keyset, self.tar, self.spi, apdu=apdu, + remote_format=self.remote_format) # add user data header tpdu = b'\x02\x70\x00' + secured # send via SMPP @@ -200,6 +211,17 @@ container_dict = dict(container) resp = container_dict.get('last_response_data') sw = container_dict.get('last_status_word') + # expanded format: decoded response carries + # per command R-APDU list; log each one. + for i, cmd in enumerate(container_dict.get('commands') or []): + logger.info("R-APDU[%u] received: %s %s", i, + cmd['response_data'], cmd['status_word']) + if container_dict.get('truncated'): + logger.warning("Response was TRUNCATED (SW 62F1): the card cut the response " + "data short and did not execute the rest of the script") + if container_dict.get('bad_format') is not None: + logger.warning("Response contains a Bad format TLV: %s", + container_dict['bad_format']) if resp is None: raise ValueError("Response does not contain any last_response_data, no R-APDU received!") if sw is None: @@ -233,8 +255,14 @@ 'por_shall_be_ciphered': not opts.por_no_ciphering, 'por_rc_cc_ds': opts.por_rc_cc_ds, 'por': opts.por_req} - apdu = h2b("".join(opts.apdu)) + if opts.format == 'expanded': + # TS 102 226 5.2.1.1: wrap each apdu in its own C-APDU TLV + apdu = [h2b(a) for a in opts.apdu] + else: + # compact: C-APDUs are concatenated as single command string + apdu = h2b("".join(opts.apdu))
- smpp_handler = SmppHandler(opts.host, opts.port, opts.system_id, opts.password, ota_keyset, spi, h2b(opts.tar)) + smpp_handler = SmppHandler(opts.host, opts.port, opts.system_id, opts.password, ota_keyset, spi, + h2b(opts.tar), remote_format=opts.format) resp, sw = smpp_handler.transceive_apdu(apdu, opts.src_addr, opts.dest_addr, opts.timeout) print("%s %s" % (b2h(resp), b2h(sw))) diff --git a/docs/smpp-ota-tool.rst b/docs/smpp-ota-tool.rst index beb494a..9dc0df6 100644 --- a/docs/smpp-ota-tool.rst +++ b/docs/smpp-ota-tool.rst @@ -170,6 +170,35 @@ .. note:: The replay-protection-counter is implemented as a 5 byte integer value (see also ETSI TS 102 225, Table 3). When the counter has reached its maximum, it will not overflow nor can it be reset.
+Expanded remote application data format +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +`smpp-ota-tool` uses the TS 102 226 section 5.1 compact remote application data format by default. This +format concatenates C-APDUs into one command string and only the result of the LAST executed command is reported back. +Retrieving the response data therefore requires a GET RESPONSE C-APDU, and only a single GET RESPONSE command may occur per script. + +The TS 102 226 section 5.2 expanded remote application data format removes these limitations: Each C-APDU is +wrapped in its own C-APDU TLV inside a Command Scripting template, and the response is a Response Scripting template that contains one R-APDU TLV with the full response data and status word per executed command. To use it, pass +``--format expanded``; every ``--apdu`` argument then becomes its own C-APDU TLV. + +.. note:: The expanded format does not use GET RESPONSE. To retrieve response data from a case 2 or case 4 + command, include an ``Le`` field in the C-APDU. i.e. ``Le='00'`` instructs the card to return all available + response data in the R-APDU, with no 256-byte limit (TS 102 226, section 5.2.1.1). Without the ``Le`` + field no response data is returned, except a status word for the last command!. + +For example, a GP GET STATUS of all applications (``80F24002024F00``) returns a registry that can be much +larger than 256 bytes. In the compact format the card would only answer with ``61xx`` procedure bytes. In the expanded +format, appending ``Le='00'`` (i.e. ``80F24002024F0000``) makes the card return the whole registry in one exchange: + +:: + + $ PYTHONPATH=./ ./contrib/smpp-ota-tool.py --kic <KIC> --kid <KID> --kid-idx 1 --kic-idx 1 \ + --algo-crypt triple_des_cbc2 --algo-auth triple_des_cbc2 --tar 000000 --cntr-req no_counter \ + --format expanded --apdu 80F24002024F0000 + +The response data (a concatenation of GlobalPlatform registry TLVs) can then be decoded with +``pySim.global_platform.GpRegistryRelatedData.from_tlv()``. + smpp-ota-tool syntax ~~~~~~~~~~~~~~~~~~~~