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('--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('--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('--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')
|
option_parser.add_argument('-a', '--apdu', action='append', required=True, type=is_hexstr, help='C-APDU to send')
|
||||||
|
|
||||||
class SmppHandler:
|
class SmppHandler:
|
||||||
@@ -77,7 +79,8 @@ class SmppHandler:
|
|||||||
|
|
||||||
def __init__(self, host: str, port: int,
|
def __init__(self, host: str, port: int,
|
||||||
system_id: str, password: str,
|
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
|
Initialize connection to SMPP server and set static OTA SMS-TPDU ciphering parameters
|
||||||
Args:
|
Args:
|
||||||
@@ -88,6 +91,7 @@ class SmppHandler:
|
|||||||
ota_keyset: OTA keyset to be used for SMS-TPDU ciphering
|
ota_keyset: OTA keyset to be used for SMS-TPDU ciphering
|
||||||
spi: Security Parameter Indicator (SPI) 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
|
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
|
# Create and connect SMPP client
|
||||||
@@ -103,6 +107,7 @@ class SmppHandler:
|
|||||||
self.ota_keyset = ota_keyset
|
self.ota_keyset = ota_keyset
|
||||||
self.tar = tar
|
self.tar = tar
|
||||||
self.spi = spi
|
self.spi = spi
|
||||||
|
self.remote_format = remote_format
|
||||||
|
|
||||||
def __del__(self):
|
def __del__(self):
|
||||||
if self.client:
|
if self.client:
|
||||||
@@ -113,14 +118,16 @@ class SmppHandler:
|
|||||||
if pdu.short_message:
|
if pdu.short_message:
|
||||||
logger.info("SMS-TPDU received: %s", b2h(pdu.short_message))
|
logger.info("SMS-TPDU received: %s", b2h(pdu.short_message))
|
||||||
try:
|
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:
|
except ValueError:
|
||||||
# Retry to decoding with ciphering disabled (in case the card has problems to decode the SMS-TDPU
|
# 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)
|
# we have sent, the response will contain an unencrypted error message)
|
||||||
spi = self.spi.copy()
|
spi = self.spi.copy()
|
||||||
spi['por_shall_be_ciphered'] = False
|
spi['por_shall_be_ciphered'] = False
|
||||||
spi['por_rc_cc_ds'] = 'no_rc_cc_ds'
|
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)
|
logger.info("SMS-TPDU decoded: %s", dec)
|
||||||
self.response = dec
|
self.response = dec
|
||||||
return None
|
return None
|
||||||
@@ -183,10 +190,14 @@ class SmppHandler:
|
|||||||
tuple containing the last response data and the last status word as byte strings
|
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
|
# 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
|
# add user data header
|
||||||
tpdu = b'\x02\x70\x00' + secured
|
tpdu = b'\x02\x70\x00' + secured
|
||||||
# send via SMPP
|
# send via SMPP
|
||||||
@@ -200,6 +211,17 @@ class SmppHandler:
|
|||||||
container_dict = dict(container)
|
container_dict = dict(container)
|
||||||
resp = container_dict.get('last_response_data')
|
resp = container_dict.get('last_response_data')
|
||||||
sw = container_dict.get('last_status_word')
|
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:
|
if resp is None:
|
||||||
raise ValueError("Response does not contain any last_response_data, no R-APDU received!")
|
raise ValueError("Response does not contain any last_response_data, no R-APDU received!")
|
||||||
if sw is None:
|
if sw is None:
|
||||||
@@ -233,8 +255,14 @@ if __name__ == '__main__':
|
|||||||
'por_shall_be_ciphered': not opts.por_no_ciphering,
|
'por_shall_be_ciphered': not opts.por_no_ciphering,
|
||||||
'por_rc_cc_ds': opts.por_rc_cc_ds,
|
'por_rc_cc_ds': opts.por_rc_cc_ds,
|
||||||
'por': opts.por_req}
|
'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)
|
resp, sw = smpp_handler.transceive_apdu(apdu, opts.src_addr, opts.dest_addr, opts.timeout)
|
||||||
print("%s %s" % (b2h(resp), b2h(sw)))
|
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).
|
.. 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.
|
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
|
smpp-ota-tool syntax
|
||||||
~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user