forked from public/pysim
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
This commit is contained in:
@@ -70,6 +70,8 @@ option_parser.add_argument("--por-req", choices=POR_REQ.decmapping.values(), def
|
||||
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 @@ class SmppHandler:
|
||||
|
||||
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 @@ class SmppHandler:
|
||||
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 @@ class SmppHandler:
|
||||
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 @@ class SmppHandler:
|
||||
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 @@ class SmppHandler:
|
||||
tuple containing the last response data and the last status word as byte strings
|
||||
"""
|
||||
|
||||
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 @@ class SmppHandler:
|
||||
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 @@ if __name__ == '__main__':
|
||||
'por_shall_be_ciphered': not opts.por_no_ciphering,
|
||||
'por_rc_cc_ds': opts.por_rc_cc_ds,
|
||||
'por': opts.por_req}
|
||||
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)))
|
||||
|
||||
@@ -170,6 +170,35 @@ ensures that a message can only be sent once.
|
||||
.. 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
|
||||
~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
|
||||
Reference in New Issue
Block a user