diff --git a/frontend/index.html b/frontend/index.html index 1dfda98..5ce6b00 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -1665,7 +1665,7 @@ // ===== Version ===== // Single source of truth for the PWA version: shown in the header and used // by the server version check in pysimConnect(). -const SIMPLE_VERSION = '3.6.7'; +const SIMPLE_VERSION = '3.6.8'; document.getElementById('app-version').textContent = 'v' + SIMPLE_VERSION; // ===== Tab switching ===== diff --git a/frontend/sw.js b/frontend/sw.js index 6d08cd9..b6e31f3 100644 --- a/frontend/sw.js +++ b/frontend/sw.js @@ -1,4 +1,4 @@ -const CACHE = 'simple-v280'; +const CACHE = 'simple-v281'; const URLS = [ 'index.html', 'help.html', diff --git a/pyproject.toml b/pyproject.toml index b2dd372..0aadf8b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "pysim-simple-server" -version = "3.6.7" +version = "3.6.8" description = "HTTP REST server wrapping pysim for the SIMple PWA" requires-python = ">=3.8" # pysim is a git-only dependency installed explicitly by setup.bat/setup.sh. diff --git a/pysim_simple_server/server.py b/pysim_simple_server/server.py index 102777e..2a365f8 100644 --- a/pysim_simple_server/server.py +++ b/pysim_simple_server/server.py @@ -31,7 +31,7 @@ from osmocom.tlv import BER_TLV_IE from osmocom.utils import rpad -VERSION = '3.6.7' +VERSION = '3.6.8' MAX_ENVELOPE_SEGMENTS = 5 # max SMS segments for outgoing C-APDU in ENVELOPE @@ -728,9 +728,14 @@ def _cap_apdu_sequence(loadfile_aid, module_aid, loadfile_data, sd_aid='', blocks (240-byte payloads, block counter in P2, last block P1=0x80), INSTALL [for install]. Shared by the SCP80 delivery path and the SCP81 command-script path; keep byte-compatible with /api/ram-install.""" - sd = sd_aid or 'A000000003000000' - ifl_data = _lv(loadfile_aid) + _lv(sd) + '00' + '00' + '00' - apdus = ['80E60200%02X%s00' % (len(ifl_data) // 2, ifl_data)] + # INSTALL/LOAD APDUs follow the reference terminal form: the Security + # Domain AID is conditional (GP Card Spec v2.3.1 Table 11-42) and is only + # sent when one was supplied (empty -> '00', the card defaults to the + # ISD), and the commands are case 3 (no trailing Le). A trailing Le made + # the card execute an extra (phantom) command whose SW 6700 masked the + # real result and, with the remote-SW check, aborted the install. + ifl_data = _lv(loadfile_aid) + (_lv(sd_aid) if sd_aid else '00') + '00' + '00' + '00' + apdus = ['80E60200%02X%s' % (len(ifl_data) // 2, ifl_data)] loadfile_tlv = 'C4' + _ber_len(len(loadfile_data) // 2) + loadfile_data # Split the TLV into consecutive 240-byte blocks (char offsets, 2 per # byte). The earlier form indexed with the block number ('i * 2'), which @@ -740,7 +745,7 @@ def _cap_apdu_sequence(loadfile_aid, module_aid, loadfile_data, sd_aid='', for off in range(0, len(loadfile_tlv), block_size * 2)] for i, block in enumerate(blocks): p1 = 0x80 if i == len(blocks) - 1 else 0x00 - apdus.append('80E8%02X%02X%02X%s00' % (p1, i % 256, len(block) // 2, block)) + apdus.append('80E8%02X%02X%02X%s' % (p1, i % 256, len(block) // 2, block)) instance = instance_aid or module_aid params = install_params if install_params else 'C900' if stk_params: @@ -748,7 +753,7 @@ def _cap_apdu_sequence(loadfile_aid, module_aid, loadfile_data, sd_aid='', p1_install = 0x0C if make_selectable else 0x04 ifi_data = (_lv(loadfile_aid) + _lv(module_aid) + _lv(instance) + _lv(privileges or '00') + _lv(params) + '00') - apdus.append('80E6%02X00%02X%s00' % (p1_install, len(ifi_data) // 2, ifi_data)) + apdus.append('80E6%02X00%02X%s' % (p1_install, len(ifi_data) // 2, ifi_data)) return apdus diff --git a/tests/test_ota_helpers.py b/tests/test_ota_helpers.py index f7a4251..4b700f1 100644 --- a/tests/test_ota_helpers.py +++ b/tests/test_ota_helpers.py @@ -1142,15 +1142,21 @@ class CapApduSequenceTest(unittest.TestCase): def test_sequence_install_load_install(self): from pysim_simple_server.server import _cap_apdu_sequence seq = _cap_apdu_sequence('A00000010001', 'A000000100', 'AABBCCDD') - # INSTALL [for load]: lv(pkg aid) + lv(ISD) + 000000 + # INSTALL [for load]: lv(pkg aid) + empty SD (ISD default) + 000000, + # case 3 - the reference terminal form (GP Card Spec 2.3.1 Table 11-42: + # the SD AID is conditional; a trailing Le makes the card execute a + # phantom second command whose SW 6700 aborts the install). self.assertEqual(seq[0], - '80E6020013' + '06A00000010001' + '08A000000003000000' + '000000' + '00') + '80E602000B' + '06A00000010001' + '00000000') # One LOAD block (small payload, last -> P1=0x80, P2=0) self.assertEqual(seq[1][:8], '80E88000') - self.assertTrue(seq[1].endswith('00')) # INSTALL [for install]: C9 00 install params appended to the lv chain self.assertTrue(seq[2].startswith('80E60C00')) self.assertIn('06A00000010001' + '05A000000100' + '05A000000100' + '0100', seq[2]) + # every RAM install APDU is case 3: length == header + Lc data, no Le + for apdu in seq: + lc = int(apdu[8:10], 16) + self.assertEqual(len(apdu), 10 + 2 * lc, apdu) def test_load_blocks_split_and_counter(self): from pysim_simple_server.server import _cap_apdu_sequence, _ber_len as _ber_len_lower @@ -1192,6 +1198,15 @@ class CapApduSequenceTest(unittest.TestCase): self.assertEqual(int(loads[0][8:10], 16), 100) self.assertEqual(int(loads[-1][8:10], 16), 4) # 704 = 7*100 + 4 + + def test_custom_sd_aid_is_included(self): + from pysim_simple_server.server import _cap_apdu_sequence + seq = _cap_apdu_sequence('A00000010001', 'A000000100', 'AABBCCDD', + sd_aid='A0000000040000') + # lv(pkg aid) + lv(custom SD) + 000000 + self.assertEqual(seq[0][:10], '80E6020012') + self.assertIn('06A00000010001' + '07A0000000040000' + '000000', seq[0]) + def test_gen_install_returns_the_apdu_list(self): # /api/scp81/gen-install: build the INSTALL/LOAD/INSTALL list for a # .cap without touching any listener or script state.