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.
This commit is contained in:
2026-09-28 02:24:12 +03:00
parent a0704a8fd0
commit 05c14b42dd
5 changed files with 77 additions and 10 deletions
+41 -4
View File
@@ -809,6 +809,18 @@
<label class="block mb-1 text-xs font-medium text-gray-700 dark:text-slate-300" data-l10n="Max services">Max services</label> <label class="block mb-1 text-xs font-medium text-gray-700 dark:text-slate-300" data-l10n="Max services">Max services</label>
<input id="rc-tk-services" oninput="updateStkParamsHex()" type="number" min="0" max="255" value="0" class="font-mono w-full border border-gray-300 dark:border-slate-600 text-xs rounded px-2 py-1.5 dark:bg-slate-800"> <input id="rc-tk-services" oninput="updateStkParamsHex()" type="number" min="0" max="255" value="0" class="font-mono w-full border border-gray-300 dark:border-slate-600 text-xs rounded px-2 py-1.5 dark:bg-slate-800">
</div> </div>
<div id="rc-tk-fsaccess-row" class="col-span-2">
<label class="flex items-center gap-2 text-xs font-medium text-gray-700 dark:text-slate-300">
<input id="rc-tk-fsaccess" type="checkbox" onchange="updateStkParamsHex()" class="rounded border-gray-300 dark:border-slate-600 dark:bg-slate-800">
<span data-l10n="File system access (full)">File system access (full)</span>
</label>
</div>
<div id="rc-tk-adfaccess-row" class="col-span-2">
<label class="flex items-center gap-2 text-xs font-medium text-gray-700 dark:text-slate-300">
<input id="rc-tk-adfaccess" type="checkbox" onchange="updateStkParamsHex()" class="rounded border-gray-300 dark:border-slate-600 dark:bg-slate-800">
<span data-l10n="ADF.USIM access (full)">ADF.USIM access (full)</span>
</label>
</div>
</div> </div>
</div> </div>
</div> </div>
@@ -1665,7 +1677,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.10'; const SIMPLE_VERSION = '3.6.11';
document.getElementById('app-version').textContent = 'v' + SIMPLE_VERSION; document.getElementById('app-version').textContent = 'v' + SIMPLE_VERSION;
// ===== Tab switching ===== // ===== Tab switching =====
@@ -2502,6 +2514,8 @@ function updateRcTkMode() {
const isSim = document.getElementById('rc-tk-mode').value === 'ca'; const isSim = document.getElementById('rc-tk-mode').value === 'ca';
document.getElementById('rc-tk-ad-row').style.display = isSim ? '' : 'none'; document.getElementById('rc-tk-ad-row').style.display = isSim ? '' : 'none';
document.getElementById('rc-tk-services-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 // 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 // `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 // 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 // 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) { function stkParamsBuild(v) {
const priority = parseInt(v.priority, 10) || 0; const priority = parseInt(v.priority, 10) || 0;
const timers = parseInt(v.timers, 10) || 0; const timers = parseInt(v.timers, 10) || 0;
@@ -2557,8 +2574,17 @@ function stkParamsBuild(v) {
mslField + mslField +
(tar ? (tar.length / 2).toString(16).padStart(2, '0') + tar : '00') + (tar ? (tar.length / 2).toString(16).padStart(2, '0') + tar : '00') +
services.toString(16).padStart(2, '0'); services.toString(16).padStart(2, '0');
const innerTlv = '80' + berLenStr(tkPayload.length / 2) + tkPayload; let eaValue = '80' + berLenStr(tkPayload.length / 2) + tkPayload;
return 'EA' + berLenStr(innerTlv.length / 2) + innerTlv; 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. // 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'), lastPos: g('rc-tk-lastpos'), lastId: g('rc-tk-lastid'),
channels: g('rc-tk-channels'), msl: g('rc-tk-msl'), 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'), 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 { } else {
html += '<div><label class="block text-gray-600 dark:text-slate-400 mb-0.5">Max services</label>' + html += '<div><label class="block text-gray-600 dark:text-slate-400 mb-0.5">Max services</label>' +
'<input type="number" min="0" max="255" value="' + escHtml(services) + '" oninput="' + uf('tkServices') + '" class="w-full font-mono border border-gray-300 dark:border-slate-600 rounded px-1.5 py-0.5 dark:bg-slate-800"></div>'; '<input type="number" min="0" max="255" value="' + escHtml(services) + '" oninput="' + uf('tkServices') + '" class="w-full font-mono border border-gray-300 dark:border-slate-600 rounded px-1.5 py-0.5 dark:bg-slate-800"></div>';
html += '<div class="col-span-4"><label class="flex items-center gap-1 text-gray-600 dark:text-slate-400">' +
'<input type="checkbox"' + (f.tkFsAccess ? ' checked' : '') + ' onchange="chainUpdateField(\'' + chainId + '\',' + idx + ',\'tkFsAccess\',this.checked);chainRender(\'' + chainId + '\')" class="rounded">' +
'File system access (full)</label></div>';
html += '<div class="col-span-4"><label class="flex items-center gap-1 text-gray-600 dark:text-slate-400">' +
'<input type="checkbox"' + (f.tkAdfAccess ? ' checked' : '') + ' onchange="chainUpdateField(\'' + chainId + '\',' + idx + ',\'tkAdfAccess\',this.checked);chainRender(\'' + chainId + '\')" class="rounded">' +
'ADF.USIM access (full)</label></div>';
} }
html += '</div></div>'; html += '</div></div>';
return html; return html;
@@ -4055,6 +4089,7 @@ function chainRamBuildRowHex(idx, row) {
lastPos: f.tkLastpos, lastId: f.tkLastid, channels: f.tkChannels, lastPos: f.tkLastpos, lastId: f.tkLastid, channels: f.tkChannels,
msl: f.tkMsl || '16', tar: f.tkTar, ad: f.tkAd, msl: f.tkMsl || '16', tar: f.tkTar, ad: f.tkAd,
services: f.tkServices, services: f.tkServices,
fsAccess: f.tkFsAccess, adfAccess: f.tkAdfAccess,
}); });
} }
// INSTALL/LOAD are case-3 commands (no trailing Le), matching the // INSTALL/LOAD are case-3 commands (no trailing Le), matching the
@@ -16391,6 +16426,8 @@ const LANG_RU = {
'Last menu ID (hex)': 'ID последнего пункта меню (hex)', 'Last menu ID (hex)': 'ID последнего пункта меню (hex)',
'Channels (max)': 'Каналы (макс)', 'Channels (max)': 'Каналы (макс)',
'Access domain (hex)': 'Домен доступа (hex)', 'Access domain (hex)': 'Домен доступа (hex)',
'File system access (full)': 'Доступ к файловой системе (полный)',
'ADF.USIM access (full)': 'Доступ к ADF.USIM (полный)',
'Load data (hex)': 'Данные загрузки (hex)', 'Load data (hex)': 'Данные загрузки (hex)',
'Block number': 'Номер блока', 'Block number': 'Номер блока',
'Encryption': 'Шифрование', 'Encryption': 'Шифрование',
+1 -1
View File
@@ -1,4 +1,4 @@
const CACHE = 'simple-v283'; const CACHE = 'simple-v284';
const URLS = [ const URLS = [
'index.html', 'index.html',
'help.html', 'help.html',
+33 -3
View File
@@ -102,7 +102,7 @@ test('every toolkit field regenerates the STK parameters hex on edit', () => {
assert.ok(/on(?:input|change)="[^"]*updateStkParamsHex/.test(m[0]), assert.ok(/on(?:input|change)="[^"]*updateStkParamsHex/.test(m[0]),
m[1] + ' does not refresh the hex: ' + 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', () => { 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', 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-textlen', 'rc-tk-menus', 'rc-tk-firstpos', 'rc-tk-firstid',
'rc-tk-lastpos', 'rc-tk-lastid', 'rc-tk-channels', 'rc-tk-msl', '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 = {}; const els = {};
for (const id of ids) els[id] = { value: '', checked: false, dataset: {} }; for (const id of ids) els[id] = { value: '', checked: false, dataset: {} };
for (const [id, v] of Object.entries(values || {})) { 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; else els[id].value = v;
} }
globalThis.document = { getElementById: id => els[id] || null }; 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'); 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', () => { test('updateStkParamsHex refreshes the field and clears the manual flag', () => {
const els = fakeForm({ 'rc-toolkit-enable': true, 'rc-tk-mode': 'ea', const els = fakeForm({ 'rc-toolkit-enable': true, 'rc-tk-mode': 'ea',
'rc-tk-tar': 'AF4D01' }); 'rc-tk-tar': 'AF4D01' });
+1 -1
View File
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
[project] [project]
name = "pysim-simple-server" name = "pysim-simple-server"
version = "3.6.10" version = "3.6.11"
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.
+1 -1
View File
@@ -31,7 +31,7 @@ from osmocom.tlv import BER_TLV_IE
from osmocom.utils import rpad 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 MAX_ENVELOPE_SEGMENTS = 5 # max SMS segments for outgoing C-APDU in ENVELOPE