fix: CAP install APDUs match the reference terminal form (v3.6.8)
INSTALL [for load]/LOAD/INSTALL [for install] carried a trailing Le and an explicit ISD AID in the Security Domain field; the card executed the extra byte as a second (phantom) command, so the compact response reported count=2 with SW 6700 - which the v3.6.2 remote-SW check correctly treated as a failure, aborting at step 1. Decrypted from the live trace (KIc/KID 25/25, 3DES): ours was 80E6020014 07F0414C46416101 08A000000003000000 00 00 00 00 the reference tool sends 80E602000C 07<aid> 00 00 00 00 (empty SD, no Le) and its response is count=1 / 9000 while ours was count=2 / 6700. - `_cap_apdu_sequence`: the SD AID is only sent when a custom one was supplied (empty -> '00', the card defaults to the ISD; GP 11.5.2.3.1 Table 11-42) and all three RAM install APDUs are case 3 (no Le). - tests: the expected INSTALL bytes updated, a no-Le assertion for every APDU in the sequence, and a custom-SD-AID case. 615 frontend / 496 Python green; version 3.6.8; sw simple-v281.
This commit is contained in:
+1
-1
@@ -1665,7 +1665,7 @@
|
|||||||
// ===== Version =====
|
// ===== Version =====
|
||||||
// Single source of truth for the PWA version: shown in the header and used
|
// Single source of truth for the PWA version: shown in the header and used
|
||||||
// by the server version check in pysimConnect().
|
// 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;
|
document.getElementById('app-version').textContent = 'v' + SIMPLE_VERSION;
|
||||||
|
|
||||||
// ===== Tab switching =====
|
// ===== Tab switching =====
|
||||||
|
|||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
const CACHE = 'simple-v280';
|
const CACHE = 'simple-v281';
|
||||||
const URLS = [
|
const URLS = [
|
||||||
'index.html',
|
'index.html',
|
||||||
'help.html',
|
'help.html',
|
||||||
|
|||||||
+1
-1
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|||||||
|
|
||||||
[project]
|
[project]
|
||||||
name = "pysim-simple-server"
|
name = "pysim-simple-server"
|
||||||
version = "3.6.7"
|
version = "3.6.8"
|
||||||
description = "HTTP REST server wrapping pysim for the SIMple PWA"
|
description = "HTTP REST server wrapping pysim for the SIMple PWA"
|
||||||
requires-python = ">=3.8"
|
requires-python = ">=3.8"
|
||||||
# pysim is a git-only dependency installed explicitly by setup.bat/setup.sh.
|
# pysim is a git-only dependency installed explicitly by setup.bat/setup.sh.
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ from osmocom.tlv import BER_TLV_IE
|
|||||||
from osmocom.utils import rpad
|
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
|
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),
|
blocks (240-byte payloads, block counter in P2, last block P1=0x80),
|
||||||
INSTALL [for install]. Shared by the SCP80 delivery path and the SCP81
|
INSTALL [for install]. Shared by the SCP80 delivery path and the SCP81
|
||||||
command-script path; keep byte-compatible with /api/ram-install."""
|
command-script path; keep byte-compatible with /api/ram-install."""
|
||||||
sd = sd_aid or 'A000000003000000'
|
# INSTALL/LOAD APDUs follow the reference terminal form: the Security
|
||||||
ifl_data = _lv(loadfile_aid) + _lv(sd) + '00' + '00' + '00'
|
# Domain AID is conditional (GP Card Spec v2.3.1 Table 11-42) and is only
|
||||||
apdus = ['80E60200%02X%s00' % (len(ifl_data) // 2, ifl_data)]
|
# 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
|
loadfile_tlv = 'C4' + _ber_len(len(loadfile_data) // 2) + loadfile_data
|
||||||
# Split the TLV into consecutive 240-byte blocks (char offsets, 2 per
|
# Split the TLV into consecutive 240-byte blocks (char offsets, 2 per
|
||||||
# byte). The earlier form indexed with the block number ('i * 2'), which
|
# 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 off in range(0, len(loadfile_tlv), block_size * 2)]
|
||||||
for i, block in enumerate(blocks):
|
for i, block in enumerate(blocks):
|
||||||
p1 = 0x80 if i == len(blocks) - 1 else 0x00
|
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
|
instance = instance_aid or module_aid
|
||||||
params = install_params if install_params else 'C900'
|
params = install_params if install_params else 'C900'
|
||||||
if stk_params:
|
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
|
p1_install = 0x0C if make_selectable else 0x04
|
||||||
ifi_data = (_lv(loadfile_aid) + _lv(module_aid) + _lv(instance) +
|
ifi_data = (_lv(loadfile_aid) + _lv(module_aid) + _lv(instance) +
|
||||||
_lv(privileges or '00') + _lv(params) + '00')
|
_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
|
return apdus
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1142,15 +1142,21 @@ class CapApduSequenceTest(unittest.TestCase):
|
|||||||
def test_sequence_install_load_install(self):
|
def test_sequence_install_load_install(self):
|
||||||
from pysim_simple_server.server import _cap_apdu_sequence
|
from pysim_simple_server.server import _cap_apdu_sequence
|
||||||
seq = _cap_apdu_sequence('A00000010001', 'A000000100', 'AABBCCDD')
|
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],
|
self.assertEqual(seq[0],
|
||||||
'80E6020013' + '06A00000010001' + '08A000000003000000' + '000000' + '00')
|
'80E602000B' + '06A00000010001' + '00000000')
|
||||||
# One LOAD block (small payload, last -> P1=0x80, P2=0)
|
# One LOAD block (small payload, last -> P1=0x80, P2=0)
|
||||||
self.assertEqual(seq[1][:8], '80E88000')
|
self.assertEqual(seq[1][:8], '80E88000')
|
||||||
self.assertTrue(seq[1].endswith('00'))
|
|
||||||
# INSTALL [for install]: C9 00 install params appended to the lv chain
|
# INSTALL [for install]: C9 00 install params appended to the lv chain
|
||||||
self.assertTrue(seq[2].startswith('80E60C00'))
|
self.assertTrue(seq[2].startswith('80E60C00'))
|
||||||
self.assertIn('06A00000010001' + '05A000000100' + '05A000000100' + '0100', seq[2])
|
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):
|
def test_load_blocks_split_and_counter(self):
|
||||||
from pysim_simple_server.server import _cap_apdu_sequence, _ber_len as _ber_len_lower
|
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[0][8:10], 16), 100)
|
||||||
self.assertEqual(int(loads[-1][8:10], 16), 4) # 704 = 7*100 + 4
|
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):
|
def test_gen_install_returns_the_apdu_list(self):
|
||||||
# /api/scp81/gen-install: build the INSTALL/LOAD/INSTALL list for a
|
# /api/scp81/gen-install: build the INSTALL/LOAD/INSTALL list for a
|
||||||
# .cap without touching any listener or script state.
|
# .cap without touching any listener or script state.
|
||||||
|
|||||||
Reference in New Issue
Block a user