v3.19.1: the compact-PoR decode guard (applet TAR responses reported as data)
pySim's CompactRemoteResp was applied to every response packet, so a third-party applet's own TAR response parsed as count=128/SW=0110/data='' (live 2026-10-04: the alfa-dsa applet's `80 01 10`) - a fabricated status word and a lost response. _decode_por now takes the plaintext command script's length (cmd_len): the compact structure's command count cannot exceed it (TS 102 226 5.1.2 Table 5.1 - each command needs at least one byte), so a larger count is classified `response_type: 'raw'`, the secured data becomes `decoded.last_response_data` (a por.data check matches it) and no SW is decoded; `secured_data` is exposed in every case. Callers pass cmd_len where they know the plaintext: the test runner's scp80 step (None for a pre-built packet; carried in ctx['last_scp80'] for the submit-PoR path), /api/send-ota (both paths), the RAM step sender and the counter probe; a caller without it keeps the previous parse. Docs: test-scripts.md and api.md describe the raw fallback. Tests: 821 frontend + 729 Python; sw cache simple-v390.
This commit is contained in:
@@ -507,6 +507,34 @@ class TestDecodePor(unittest.TestCase):
|
||||
msg='expected None for %r' % bad)
|
||||
|
||||
|
||||
def test_applet_response_falls_back_to_raw(self):
|
||||
# A third-party applet's own TAR answers with application bytes;
|
||||
# `80 01 10` cannot be a compact response for a 1-byte command script
|
||||
# (count 0x80 exceeds it), so the whole secured data is the response
|
||||
# data and no status word is invented (live 2026-10-04).
|
||||
raw = '027100000e0aaf4d0100020000190000800110'
|
||||
r = _decode_por('16', '01', '15', '15', '0000000001', K, K, raw, cmd_len=1)
|
||||
self.assertEqual(r['response_type'], 'raw')
|
||||
self.assertEqual(r['secured_data'], '800110')
|
||||
self.assertEqual(r['decoded']['last_status_word'], '')
|
||||
self.assertEqual(r['decoded']['last_response_data'], '800110')
|
||||
self.assertIsNone(r['decoded']['number_of_commands'])
|
||||
# without the command length the compact parse is kept (unknown
|
||||
# context - the pre-fix behaviour)
|
||||
r = _decode_por('16', '01', '15', '15', '0000000001', K, K, raw)
|
||||
self.assertEqual(r['response_type'], 'compact')
|
||||
self.assertEqual(r['decoded']['number_of_commands'], 128)
|
||||
|
||||
def test_rfm_chain_response_stays_compact(self):
|
||||
# a chained RFM command script is long enough for its count: the real
|
||||
# SW (6a86) is decoded as before
|
||||
raw = '027100000e0ab0000000020000180000016a86'
|
||||
r = _decode_por('16', '01', '15', '15', '0000000001', K, K, raw, cmd_len=68)
|
||||
self.assertEqual(r['response_type'], 'compact')
|
||||
self.assertEqual(r['decoded']['last_status_word'], '6a86')
|
||||
self.assertEqual(r['secured_data'], '016A86')
|
||||
|
||||
|
||||
class TestProactiveDecode(unittest.TestCase):
|
||||
"""Server-side proactive command/TR decode helpers (v1.8.0 log feature)."""
|
||||
|
||||
|
||||
@@ -1097,6 +1097,32 @@ class TestRunnerActions(RunnerTestCase):
|
||||
self.assertIn('TEST-RUN step 2: SMS DA=79332505884 PID=00 DCS=00 UDL=8 '
|
||||
'UD=4B1C12579C9D83', log)
|
||||
|
||||
# the live alfa-dsa applet's response packet (2026-10-04): the secured
|
||||
# data `80 01 10` is the applet's own - the compact structure's command
|
||||
# count (0x80) cannot fit the 1-byte command script
|
||||
APP_POR_RAW = '027100000e0aaf4d0100020000190000800110'
|
||||
|
||||
def test_scp80_por_object_check_on_an_applet_response(self):
|
||||
# the live alfa-dsa applet's response (2026-10-04): the secured data
|
||||
# `80 01 10` is the applet's own, so the compact parse must not claim
|
||||
# 128 commands - the runner exposes it as the PoR data
|
||||
preset = {'kic': '15', 'kid': '15', 'kicKey': 'AA' * 16,
|
||||
'kidKey': 'BB' * 16, 'counter': '00000001',
|
||||
'tars': preset_tars()}
|
||||
with mock.patch.object(S, '_build_secured_packet', return_value=('AA' * 10, {})), \
|
||||
mock.patch.object(S, '_send_secured_packet',
|
||||
return_value={'success': True, 'sw': '9000',
|
||||
'response_data': self.APP_POR_RAW}):
|
||||
run = self.run_script(FakeServer(FakeScc()), [
|
||||
{'type': 'action', 'kind': 'scp80', 'params': {'apdu': '01'},
|
||||
'check': {'por': {'status': 'por_ok', 'data': '800110'}}},
|
||||
], preset)
|
||||
self.assertEqual(run['status'], 'ok', run['steps'])
|
||||
labels = {c['label']: c for c in run['steps'][0]['checks']}
|
||||
self.assertTrue(labels['PoR status']['ok'], labels)
|
||||
self.assertTrue(labels['PoR data']['ok'], labels)
|
||||
self.assertEqual(run['steps'][0]['por']['response_type'], 'raw')
|
||||
|
||||
def test_scp80_format_wraps_the_apdu(self):
|
||||
# `format` wraps the C-APDU in the expanded Command Scripting template
|
||||
# (TS 102 226 5.2.1): `expanded` = AA/<len>/22/..., `expanded-ae` =
|
||||
|
||||
Reference in New Issue
Block a user