fix: restore MF after fast-init probing so eUICC cards init cleanly (v2.8.1)
With an eUICC in the reader the fast init raised SwMatchError twice and fell back to the stock pysim init: pick_profile_no_reset() disables the physical resets that CardProfile.match_with_card() normally performs, so the successful SGP.22 probe leaves the ISD-R ADF selected. RuntimeState then selects MF by FID (00 A4 00 04 02 3F00) from within the ADF, which this card answers with 6A82. - _restore_mf_after_probe() re-selects MF after the profile pick: the cheap select keeps the reset-free path for normal cards, a physical reset covers cards that refuse the MF select from an ADF. - The EID read restores with rs.reset() (soft reset, escalating to a physical reset) instead of rs.soft_reset(), which had the same trap. - tests: FakeScc mf_select_error mode + two _restore_mf_after_probe cases (no reset / exactly one physical reset).
This commit is contained in:
+1
-1
@@ -1436,7 +1436,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 = '2.8.0';
|
||||
const SIMPLE_VERSION = '2.8.1';
|
||||
document.getElementById('app-version').textContent = 'v' + SIMPLE_VERSION;
|
||||
|
||||
// ===== Tab switching =====
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
const CACHE = 'simple-v225';
|
||||
const CACHE = 'simple-v226';
|
||||
const URLS = [
|
||||
'index.html',
|
||||
'help.html',
|
||||
|
||||
+1
-1
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
||||
|
||||
[project]
|
||||
name = "pysim-simple-server"
|
||||
version = "2.8.0"
|
||||
version = "2.8.1"
|
||||
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.
|
||||
|
||||
@@ -80,6 +80,18 @@ def pick_profile_no_reset(scc):
|
||||
scc.reset_card = original_reset
|
||||
|
||||
|
||||
def _restore_mf_after_probe(scc):
|
||||
"""Probing can leave an ADF (e.g. ISD-R on an eUICC) selected. The
|
||||
RuntimeState construction selects MF by FID, which some cards refuse from
|
||||
within an ADF (6A82), so restore it here: the cheap select keeps the
|
||||
reset-free path for normal cards, the physical reset covers the rest."""
|
||||
try:
|
||||
scc.select_file('3f00')
|
||||
except SwMatchError:
|
||||
sys.stderr.write('FAST-INIT: MF restore after probing failed; physical reset\n')
|
||||
scc.reset_card()
|
||||
|
||||
|
||||
def init_card_fast(sl, skip_card_init=False, wait=True):
|
||||
"""Replacement for pySim.app.init_card() that avoids redundant resets.
|
||||
|
||||
@@ -112,6 +124,11 @@ def _init_card_once(sl, skip_card_init, wait):
|
||||
if profile is None:
|
||||
return None, card
|
||||
|
||||
# A successful probe may leave an ADF selected (e.g. ISD-R on an eUICC);
|
||||
# RuntimeState selects MF by FID and would fail on cards that refuse that
|
||||
# from within an ADF.
|
||||
_restore_mf_after_probe(scc)
|
||||
|
||||
if generic_card and isinstance(profile, CardProfileUICC):
|
||||
card._adm_chv_num = 0x0A
|
||||
|
||||
@@ -137,7 +154,9 @@ def _init_card_once(sl, skip_card_init, wait):
|
||||
except SwMatchError:
|
||||
pass
|
||||
finally:
|
||||
rs.soft_reset()
|
||||
# rs.reset() tries the reset-free soft reset first and escalates
|
||||
# to a physical reset when MF cannot be selected from the ADF.
|
||||
rs.reset()
|
||||
|
||||
return rs, card
|
||||
|
||||
|
||||
@@ -27,7 +27,7 @@ from osmocom.tlv import BER_TLV_IE
|
||||
from osmocom.utils import rpad
|
||||
|
||||
|
||||
VERSION = '2.8.0'
|
||||
VERSION = '2.8.1'
|
||||
|
||||
MAX_ENVELOPE_SEGMENTS = 5 # max SMS segments for outgoing C-APDU in ENVELOPE
|
||||
|
||||
|
||||
+18
-1
@@ -24,17 +24,21 @@ from pysim_simple_server.fastinit import (
|
||||
|
||||
|
||||
class FakeScc:
|
||||
def __init__(self):
|
||||
def __init__(self, mf_select_error=False):
|
||||
self.sel_ctrl = '0004'
|
||||
self.cla_byte = '00'
|
||||
self.resets = 0
|
||||
self.selected = []
|
||||
self.mf_select_error = mf_select_error
|
||||
|
||||
def reset_card(self):
|
||||
self.resets += 1
|
||||
|
||||
def select_file(self, fid):
|
||||
self.selected.append(fid)
|
||||
if fid == '3f00' and self.mf_select_error:
|
||||
# eUICC with the ISD-R ADF selected: MF is not selectable by FID
|
||||
raise SwMatchError('6a82', '9000')
|
||||
return ('', '9000')
|
||||
|
||||
def select_adf(self, aid):
|
||||
@@ -56,6 +60,19 @@ class TestPickProfileNoReset(unittest.TestCase):
|
||||
self.assertEqual(scc.resets, 1)
|
||||
|
||||
|
||||
class TestRestoreMfAfterProbe(unittest.TestCase):
|
||||
def test_normal_card_keeps_the_reset_free_path(self):
|
||||
scc = FakeScc()
|
||||
fastinit._restore_mf_after_probe(scc)
|
||||
self.assertEqual(scc.resets, 0)
|
||||
self.assertEqual(scc.selected[-1], '3f00')
|
||||
|
||||
def test_adf_selected_card_falls_back_to_a_physical_reset(self):
|
||||
scc = FakeScc(mf_select_error=True)
|
||||
fastinit._restore_mf_after_probe(scc)
|
||||
self.assertEqual(scc.resets, 1)
|
||||
|
||||
|
||||
class FakeLchan:
|
||||
def __init__(self):
|
||||
self.scc = types.SimpleNamespace(scp=object())
|
||||
|
||||
Reference in New Issue
Block a user