v3.20.0 (fix): the install-parameter composition - one System Specific Parameters EF
Two reported SCP80/RAM issues, both confirmed against the specs:
- docs/api.md called `stk_params` a "Hex CA TLV", but TS 102 226 8.2.1.3.2.1
puts the CA *inside* the System Specific Parameters EF ('EF'), and the PWA
builds `EF <len> CA <len> ...` (the SIM access domain leads the CA value as
`01 <ad>` / `00`). A client following the doc sent a CA at the wrong level.
- the RAM composition appended the STK part after a caller-composed
C9+EF(quotas) prefix, producing TWO EF objects. The card read the first
(the quotas) and ignored the CA: the install was accepted (por_ok) but the
toolkit parameters were never registered, so no STK menu appeared (live
2026-10-05). Per GPD_SPE_013 v1.1 Table 6-5 (pySim's
gen_install_parameters) the quotas (C7/C8) and the CA share ONE EF; an
EA-form STK part is a sibling of EF (the reference TCA loader's
`C9 00 EF 00 EA ...`).
- server: `_compose_install_params()` (used by `_cap_install_apdu` ->
`_cap_apdu_sequence`, hence /api/ram-install, /api/ram-install-app,
/api/scp81/gen-install and the PWA's install script template) enforces the
rule: a caller-composed EF plus an EF-form `stk_params`, a bare CA, a
malformed EF and quotas next to a non-empty `install_params` are refused
(400 / `{"ok": false}` with the fix named); with an empty `install_params`
the quotas and an EF-form CA are composed into one EF
(`C900 EF{C7?,C8?,CA}`), an EA part stays a sibling.
- the documented `nv_quota`/`volatile_quota` fields are now real (they were
named in the docs but implemented nowhere): integers composing C8/C7.
- PWA: `composeRamInstallParams()` composes the same single EF itself (a
CA-form STK field takes the quotas into its EF, the merged EF travels in
`stk_params`), the RAM install bodies send it, the placeholder shows it
with a note when the quotas moved into the STK EF, and a refused request
(no steps) reports the error instead of "FAILED at step: undefined".
- docs/api.md: the corrected `stk_params` description with both accepted
shapes, the composition rule paragraph with the refusal cases, the fixed
example (`C90000` -> `C900`), and the /api/ram-install-app and
/api/scp81/gen-install cross-references.
- tests: composition unit tests (merge/refusals/EA-sibling) and the
ram-install-app 400 paths (Python); `ramQuotaTlvs`/`composeRamInstallParams`/
`ramInstallParamsCompose` pins incl. the CA merge (Node).
Tests: 824 frontend + 740 Python; sw cache simple-v395.
This commit is contained in:
@@ -715,6 +715,31 @@ class PresetStoreHttpTests(unittest.TestCase):
|
||||
self.assertEqual(resp['final_cntr'], '0000000002')
|
||||
self.assertEqual(self.store.get(pid)['keysets'][0]['cntr'], '0000000002')
|
||||
|
||||
def test_ram_install_app_rejects_the_two_ef_combination(self):
|
||||
"""v3.20.0: an EF-form STK part next to a caller-composed EF is
|
||||
refused before anything is built (TS 102 226 8.2.1.3.2.1)."""
|
||||
status, resp = self._post('/api/presets', self._preset(iccid='8970119000004600098'))
|
||||
self.assertEqual(status, 200, resp)
|
||||
pid = resp['preset']['id']
|
||||
self.server.app = object()
|
||||
status, resp = self._post('/api/ram-install-app', {
|
||||
'mode': 'install',
|
||||
'preset_id': pid, 'kic': '15', 'kid': '15',
|
||||
'kicKey': 'AA', 'kidKey': 'BB',
|
||||
'loadfile_aid': 'F0414C46416101', 'module_aid': 'F0414C4641610101',
|
||||
'install_params': 'C900EF04C7020064', 'stk_params': 'EF04CA0201F0'})
|
||||
self.assertEqual(status, 400, resp)
|
||||
self.assertIn('second System Specific Parameters', resp['error'])
|
||||
# quotas next to a caller-composed install_params are refused too
|
||||
status, resp = self._post('/api/ram-install-app', {
|
||||
'mode': 'install',
|
||||
'preset_id': pid, 'kic': '15', 'kid': '15',
|
||||
'kicKey': 'AA', 'kidKey': 'BB',
|
||||
'loadfile_aid': 'F0414C46416101', 'module_aid': 'F0414C4641610101',
|
||||
'install_params': 'C900EF04C7020064', 'volatile_quota': 100})
|
||||
self.assertEqual(status, 400, resp)
|
||||
self.assertIn('composed only when install_params is empty', resp['error'])
|
||||
|
||||
def test_create_update_delete_round_trip(self):
|
||||
status, resp = self._post('/api/presets', self._preset(iccid='8970119000004600098'))
|
||||
self.assertEqual(status, 200, resp)
|
||||
|
||||
Reference in New Issue
Block a user