From 05c14b42dd12b0e58466c0dfe638a46a8935627c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=90=D0=BD=D1=82=D0=BE=D0=BD=20=D0=A2=D1=80=D0=BE=D1=88?= =?UTF-8?q?=D0=B8=D0=BD?= Date: Mon, 28 Sep 2026 02:24:12 +0300 Subject: [PATCH] feat: UICC file-access parameters in the install form (v3.6.11) SIM toolkit applets installed while UICC-library applets failed at INSTALL [for install] with 6F00 - including with the reference tool's exact install parameters. The asymmetry: the SIM (CA) path grants file access via the Access Domain field (default 00 = full access), while the UICC (EA) path sent no '82' (UICC Access Application specific parameters) at all, and the reference's '82 00' is empty. An applet importing uicc.access (the failing CAP has 2 refs) can then fail inside its install(). - EA mode gains two checkboxes (RAM install form + the chain rows' toolkit block): "File system access (full)" appends '82 03 00 01 00' (shared file system + Access Domain Parameter 00 = full access, TS 102 226 8.2.1.3.2.2.2/8.2.1.3.2.5); "ADF.USIM access (full)" adds '07 A0000000871002 01 00' to it. Both default off. - tests: the access TLV shapes (with/without the ADF entry), the form -> hex path with the checkbox, and the toolkit-field wiring count (16 fields). 635 frontend / 496 Python green; version 3.6.11; sw simple-v284. --- frontend/index.html | 45 ++++++++++++++++++++++++++++--- frontend/sw.js | 2 +- frontend/tests/stk_params.test.js | 36 ++++++++++++++++++++++--- pyproject.toml | 2 +- pysim_simple_server/server.py | 2 +- 5 files changed, 77 insertions(+), 10 deletions(-) diff --git a/frontend/index.html b/frontend/index.html index 760204a..e82f91f 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -809,6 +809,18 @@ +
+ +
+
+ +
@@ -1665,7 +1677,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.10'; +const SIMPLE_VERSION = '3.6.11'; document.getElementById('app-version').textContent = 'v' + SIMPLE_VERSION; // ===== Tab switching ===== @@ -2502,6 +2514,8 @@ function updateRcTkMode() { const isSim = document.getElementById('rc-tk-mode').value === 'ca'; document.getElementById('rc-tk-ad-row').style.display = isSim ? '' : 'none'; document.getElementById('rc-tk-services-row').style.display = isSim ? 'none' : ''; + document.getElementById('rc-tk-fsaccess-row').style.display = isSim ? 'none' : ''; + document.getElementById('rc-tk-adfaccess-row').style.display = isSim ? 'none' : ''; } // Pure SIM/UICC toolkit install-parameter builder shared by the RAM install @@ -2509,7 +2523,10 @@ function updateRcTkMode() { // `v` carries the field values; returns the CA/EA TLV hex, or null when a // value cannot be coded: each TAR is 3 bytes (6 hex digits), and menu item // identifiers 128..255 are reserved for the toolkit framework, so only -// 01..7F may be requested (TS 102 226 8.2.1.3.2.3). +// 01..7F may be requested (TS 102 226 8.2.1.3.2.3). In EA mode `v.fsAccess` +// adds the UICC file-access parameters (tag '82') and `v.adfAccess` extends +// them with an ADF.USIM entry; the SIM path grants access via the CA Access +// Domain field instead. function stkParamsBuild(v) { const priority = parseInt(v.priority, 10) || 0; const timers = parseInt(v.timers, 10) || 0; @@ -2557,8 +2574,17 @@ function stkParamsBuild(v) { mslField + (tar ? (tar.length / 2).toString(16).padStart(2, '0') + tar : '00') + services.toString(16).padStart(2, '0'); - const innerTlv = '80' + berLenStr(tkPayload.length / 2) + tkPayload; - return 'EA' + berLenStr(innerTlv.length / 2) + innerTlv; + let eaValue = '80' + berLenStr(tkPayload.length / 2) + tkPayload; + if (v.fsAccess) { + // UICC Access Application specific parameters (TS 102 226 + // 8.2.1.3.2.2.2): [file system AID length 00 = shared file system] + // [Access Domain length 01][ADP 00 = full access], optionally with an + // ADF entry [AID length 07][ADF.USIM][AD length 01][ADP 00]. + let acc = '000100'; + if (v.adfAccess) acc += '07A00000008710020100'; + eaValue += '82' + berLenStr(acc.length / 2) + acc; + } + return 'EA' + berLenStr(eaValue.length / 2) + eaValue; } // The RAM install form's toolkit fields -> the install parameters hex. @@ -2573,6 +2599,8 @@ function buildRcToolkitParams() { lastPos: g('rc-tk-lastpos'), lastId: g('rc-tk-lastid'), channels: g('rc-tk-channels'), msl: g('rc-tk-msl'), tar: g('rc-tk-tar'), ad: g('rc-tk-ad'), services: g('rc-tk-services'), + fsAccess: document.getElementById('rc-tk-fsaccess').checked, + adfAccess: document.getElementById('rc-tk-adfaccess').checked, }); } @@ -3437,6 +3465,12 @@ function chainRamToolkitHtml(chainId, idx, f, uf) { } else { html += '
' + '
'; + html += '
'; + html += '
'; } html += ''; return html; @@ -4055,6 +4089,7 @@ function chainRamBuildRowHex(idx, row) { lastPos: f.tkLastpos, lastId: f.tkLastid, channels: f.tkChannels, msl: f.tkMsl || '16', tar: f.tkTar, ad: f.tkAd, services: f.tkServices, + fsAccess: f.tkFsAccess, adfAccess: f.tkAdfAccess, }); } // INSTALL/LOAD are case-3 commands (no trailing Le), matching the @@ -16391,6 +16426,8 @@ const LANG_RU = { 'Last menu ID (hex)': 'ID последнего пункта меню (hex)', 'Channels (max)': 'Каналы (макс)', 'Access domain (hex)': 'Домен доступа (hex)', + 'File system access (full)': 'Доступ к файловой системе (полный)', + 'ADF.USIM access (full)': 'Доступ к ADF.USIM (полный)', 'Load data (hex)': 'Данные загрузки (hex)', 'Block number': 'Номер блока', 'Encryption': 'Шифрование', diff --git a/frontend/sw.js b/frontend/sw.js index 89819fa..bd5f77b 100644 --- a/frontend/sw.js +++ b/frontend/sw.js @@ -1,4 +1,4 @@ -const CACHE = 'simple-v283'; +const CACHE = 'simple-v284'; const URLS = [ 'index.html', 'help.html', diff --git a/frontend/tests/stk_params.test.js b/frontend/tests/stk_params.test.js index 03d334c..f9ac60d 100644 --- a/frontend/tests/stk_params.test.js +++ b/frontend/tests/stk_params.test.js @@ -102,7 +102,7 @@ test('every toolkit field regenerates the STK parameters hex on edit', () => { assert.ok(/on(?:input|change)="[^"]*updateStkParamsHex/.test(m[0]), m[1] + ' does not refresh the hex: ' + m[0]); } - assert.strictEqual(seen.length, 14, 'expected 14 toolkit fields, got ' + seen.join(', ')); + assert.strictEqual(seen.length, 16, 'expected 16 toolkit fields, got ' + seen.join(', ')); }); test('the applet TAR field has no B00001 default or placeholder', () => { @@ -127,11 +127,13 @@ function fakeForm(values) { const ids = ['rc-toolkit-enable', 'rc-tk-mode', 'rc-tk-priority', 'rc-tk-timers', 'rc-tk-textlen', 'rc-tk-menus', 'rc-tk-firstpos', 'rc-tk-firstid', 'rc-tk-lastpos', 'rc-tk-lastid', 'rc-tk-channels', 'rc-tk-msl', - 'rc-tk-tar', 'rc-tk-ad', 'rc-tk-services', 'ram-stk-params']; + 'rc-tk-tar', 'rc-tk-ad', 'rc-tk-services', 'rc-tk-fsaccess', + 'rc-tk-adfaccess', 'ram-stk-params']; + const checks = ['rc-toolkit-enable', 'rc-tk-fsaccess', 'rc-tk-adfaccess']; const els = {}; for (const id of ids) els[id] = { value: '', checked: false, dataset: {} }; for (const [id, v] of Object.entries(values || {})) { - if (id === 'rc-toolkit-enable') els[id].checked = v; + if (checks.indexOf(id) >= 0) els[id].checked = !!v; else els[id].value = v; } globalThis.document = { getElementById: id => els[id] || null }; @@ -147,6 +149,34 @@ test('the RAM form fields build the live install parameters end to end', () => { assert.strictEqual(buildRcToolkitParams(), 'EA0F800D000000000102011203AF4D0100'); }); +test('UICC file-access parameters (82) are appended in EA mode', () => { + // TS 102 226 8.2.1.3.2.2.2: [file system AID len 00 = shared FS] + // [Access Domain len 01][ADP 00 = full access]; the SIM path grants the + // same rights via the CA Access Domain field. The ADF entry is an + // extension of the file-system entry. + const base = vals({ channels: '1', msl: '12', tar: 'AF4D01' }); + assert.strictEqual(stkParamsBuild(Object.assign({}, base, { fsAccess: true })), + 'EA14800D000000000102011203AF4D01008203000100'); + assert.strictEqual( + stkParamsBuild(Object.assign({}, base, { fsAccess: true, adfAccess: true })), + 'EA1E800D000000000102011203AF4D0100820D00010007A00000008710020100'); + assert.strictEqual(stkParamsBuild(base), 'EA0F800D000000000102011203AF4D0100'); + assert.strictEqual(stkParamsBuild(Object.assign({}, base, { adfAccess: true })), + 'EA0F800D000000000102011203AF4D0100'); +}); + +test('the RAM form emits full file access when the checkbox is ticked', () => { + fakeForm({ 'rc-toolkit-enable': true, 'rc-tk-mode': 'ea', 'rc-tk-msl': '12', + 'rc-tk-tar': 'AF4D01', 'rc-tk-channels': '1', 'rc-tk-fsaccess': true }); + assert.strictEqual(buildRcToolkitParams(), + 'EA14800D000000000102011203AF4D01008203000100'); + fakeForm({ 'rc-toolkit-enable': true, 'rc-tk-mode': 'ea', 'rc-tk-msl': '12', + 'rc-tk-tar': 'AF4D01', 'rc-tk-channels': '1', 'rc-tk-fsaccess': true, + 'rc-tk-adfaccess': true }); + assert.strictEqual(buildRcToolkitParams(), + 'EA1E800D000000000102011203AF4D0100820D00010007A00000008710020100'); +}); + test('updateStkParamsHex refreshes the field and clears the manual flag', () => { const els = fakeForm({ 'rc-toolkit-enable': true, 'rc-tk-mode': 'ea', 'rc-tk-tar': 'AF4D01' }); diff --git a/pyproject.toml b/pyproject.toml index ed757bd..1f9d0a3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "pysim-simple-server" -version = "3.6.10" +version = "3.6.11" 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 3729fd2..ff26824 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.10' +VERSION = '3.6.11' MAX_ENVELOPE_SEGMENTS = 5 # max SMS segments for outgoing C-APDU in ENVELOPE