Infer the card's ETSI release from its installed uicc.toolkit version
The explorer's card-resources block now names the card's ETSI release, using the same mapping as the CAP analysis (capmem.ETSI_TOOLKIT_RELEASE: uicc.toolkit version -> REL-N, basis in brackets; the live card's 1.12 -> REL-15/16/17). The table is mirrored in the PWA (drift-guarded by tests/test_cap_memory.py, like JC_FRAMEWORK_SDK); the version comes from the expanded registry's CE tag and degrades to the version hint when the listing has none; SIM-only cards (no uicc.toolkit) get no label, the same rule as SIM-only CAPs. - cardEtsiRelease(elfs) (pure) + UICC_TOOLKIT_AID / ETSI_TOOLKIT_RELEASE in the PWA; rendered after the free-memory lines with a tooltip naming the basis. - Tests: helper vectors (1.12/1.11/1.5, unknown version, versionless, SIM-only), the rendering guard, and the table/AID drift guard. - Help EN/RU; sw cache simple-v354.
This commit is contained in:
@@ -314,7 +314,7 @@
|
||||
<p class="text-sm mb-3">Кнопка <strong>Проверить в pySim</strong> сверяет собранный пакет с эталонной реализацией <code class="font-mono text-sm">OtaDialectSms.encode_cmd</code>. Кнопка <strong>Отправить на карту</strong> доставляет пакет через ENVELOPE SMS-PP-DOWNLOAD (при подключении к серверу). 2-байтовый префикс CPL передаётся, когда пакет зашифрован или несёт RC/CC/DS (входит в их входные данные), а также при конкатенации SMS; одиночное незащищённое SMS сохраняет форму pySim, начинающуюся с CHL (TS 31.115 Table 1 NOTE). Под полем пакета показывается его размер и число SMS; пакет, не помещающийся в одно SMS, отправляется <strong>конкатенированной</strong> загрузкой (TS 31.115 §4.3) — пакет делится на части SMS-пользовательских данных (первое SMS 132 октета, последующие по 134; первое дополнительно несёт информационные элементы конкатенации и CPI), и сегменты отправляются по порядку. Более 5 сегментов отклоняется: это практический предел буфера конкатенации карты. Полученный Proof of Receipt декодируется и показывается строкой статуса PoR (статус, TAR, счётчик, сырой PoR); статусное слово и данные ответа последней команды подставляются в подвкладку <strong>«Разбор R-APDU»</strong> (Remote APDU → «Разбор»), а успешный PoR увеличивает счётчик повторов (когда в SPI1 есть проверка счётчика — b5 b4 ≠ 00; без неё счётчик не увеличивается и не сохраняется) и очищает пакет.</p>
|
||||
|
||||
<h3 id="ram" class="text-lg font-medium mb-2">3.2 RAM</h3>
|
||||
<p class="mb-2">Выполняет операции удалённого управления приложениями (Remote Application Management) как защищённые пакеты SCP80 через SMS-PP-DOWNLOAD ENVELOPE. Карта должна поддерживать SCP03 (AES или 3DES). Пресет карты со вкладки <strong>Карты</strong> обеспечивает SPI, ключи, TAR и счётчик (RAM использует <strong>ISD TAR</strong> пресета, по умолчанию <code class="font-mono text-sm">000000</code>). Селектор <strong>Keyset</strong> рядом с выбором пресета указывает, из какого keyset берутся ключи и счётчик операции. Селектор недоступен, пока не выбран пресет карты. Кнопка запуска недоступна, пока не выбран пресет карты и один из его keysets. Селектор <strong>Формат</strong> фиксирует формат удалённой команды: <strong>Авто (определение)</strong> опрашивает read-only команду GET STATUS в компактном виде и в обоих expanded-кодированиях и оставляет первое, которое карта выполнила; <strong>Компактный</strong> отправляет голую C-APDU; <strong>Expanded (AA, определённая длина)</strong> / <strong>Expanded (AE 80, неопределённая длина)</strong> заворачивают её в шаблон Command Scripting из TS 102 226 §5.2.1 (на живой карте выполняются обе; шаблон, у которого заявленная длина не совпадает с телом, получает Bad format TLV — операция показывает это как ошибку шага). Запросы листингов и реестра следуют этому формату: компактные листинги несут сцепленный GET RESPONSE, а expanded-прогон отправляет те же запросы P2=00 обёрнутыми и без цепочки (форма эталонного загрузчика TCA; Le=’00’ возвращает листинг сразу), и запрос реестра, отклонённый картой в текущем формате, повторяется в остальных — отчёт называет тот, который сработал.</p>
|
||||
<p class="mb-2">Выполняет операции удалённого управления приложениями (Remote Application Management) как защищённые пакеты SCP80 через SMS-PP-DOWNLOAD ENVELOPE. Карта должна поддерживать SCP03 (AES или 3DES). Пресет карты со вкладки <strong>Карты</strong> обеспечивает SPI, ключи, TAR и счётчик (RAM использует <strong>ISD TAR</strong> пресета, по умолчанию <code class="font-mono text-sm">000000</code>). Селектор <strong>Keyset</strong> рядом с выбором пресета указывает, из какого keyset берутся ключи и счётчик операции. Селектор недоступен, пока не выбран пресет карты. Кнопка запуска недоступна, пока не выбран пресет карты и один из его keysets. Селектор <strong>Формат</strong> фиксирует формат удалённой команды: <strong>Авто (определение)</strong> опрашивает read-only команду GET STATUS в компактном виде и в обоих expanded-кодированиях и оставляет первое, которое карта выполнила; <strong>Компактный</strong> отправляет голую C-APDU; <strong>Expanded (AA, определённая длина)</strong> / <strong>Expanded (AE 80, неопределённая длина)</strong> заворачивают её в шаблон Command Scripting из TS 102 226 §5.2.1 (на живой карте выполняются обе; шаблон, у которого заявленная длина не совпадает с телом, получает Bad format TLV — операция показывает это как ошибку шага). Запросы листингов и реестра следуют этому формату: компактные листинги несут сцепленный GET RESPONSE, а expanded-прогон отправляет те же запросы P2=00 обёрнутыми и без цепочки (форма эталонного загрузчика TCA; Le=’00’ возвращает листинг сразу), и запрос реестра, отклонённый картой в текущем формате, повторяется в остальных — отчёт называет тот, который сработал. Блок ресурсов карты также называет предполагаемый <strong>релиз ETSI</strong> карты (версия <code class="font-mono text-sm">uicc.toolkit</code> → REL-N, то же сопоставление, что и в анализе CAP; показывается, только если расширенный реестр вернул версии).</p>
|
||||
|
||||
<h4 id="ram-operations" class="font-medium mb-1">Операции</h4>
|
||||
<table class="w-full text-sm mb-3 border-collapse">
|
||||
|
||||
+1
-1
@@ -313,7 +313,7 @@
|
||||
<p class="text-sm mb-3">A “Verify vs pySim” button cross-checks the assembled packet against pySim’s reference <code class="font-mono text-sm">OtaDialectSms.encode_cmd</code>. A “Send to Card” button delivers it via SMS-PP-DOWNLOAD ENVELOPE (when connected to the server). The 2-byte CPL prefix is transmitted whenever the packet is ciphered or carries RC/CC/DS (it is part of their input) and whenever the packet is concatenated; a single unprotected SMS keeps pySim’s CHL-first form (TS 31.115 Table 1 NOTE). Under the packet field the app reports its size and how many SMS it takes; a packet that does not fit one SMS is sent as a <strong>concatenated</strong> download (TS 31.115 §4.3) — the packet is split into SMS user-data parts (first SM 132 octets, following ones 134; the first one additionally carries the concatenation and CPI information elements) and the segments are sent in order. More than 5 segments is refused, that being the practical limit of the card’s concatenation buffer. The returned Proof of Receipt is decoded and shown as a PoR status line (status, TAR, counter, raw PoR); the last command’s status word and response data are filled into the <strong>R-APDU parser</strong> panel, and a successful PoR advances the replay counter (when SPI1 carries a counter check — b5 b4 ≠ 00; without one no counter is advanced or saved) and clears the packet. A <strong>low-counter</strong> verdict (<code class="font-mono text-sm">cntr_low</code>, or a warning ENVELOPE answer that still carries such a PoR) is <em>not</em> recoverable by retrying: every SCP80/RAM operation stops immediately. The card never reports its counter (the PoR’s CNTR field is a copy of the packet’s), so the notice offers a bounded <strong>Sync counter</strong> probe — a read-only command with increasing counter values (doubling steps, a 32-bit ceiling, at most 40 packets) until the card accepts one, then accepted+1 is stored — and a <strong>Go to preset</strong> button for raising the counter by hand; nothing else is written automatically.</p>
|
||||
|
||||
<h3 id="ram" class="text-lg font-medium mb-2">3.2 RAM</h3>
|
||||
<p class="mb-2">Delivers Remote Application Management operations as SCP80 secured packets via SMS-PP-DOWNLOAD ENVELOPE. The card must support SCP03 (AES or 3DES). A saved card preset from the <strong>Cards</strong> tab provides the SPI, keys, TAR, and counter (RAM uses the preset's <strong>ISD TAR</strong>, <code class="font-mono text-sm">000000</code> by default). The <strong>Keyset</strong> selector next to the preset selector picks the keyset whose keys and counter the operation uses. It is disabled until a card preset is selected. The start button stays disabled until a card preset and one of its keysets are selected. The <strong>Format</strong> selector pins the remote command format: <strong>Auto (detect)</strong> probes the read-only GET STATUS command compact and in both expanded codings and keeps the first that the card executes, <strong>Compact</strong> sends the bare C-APDU, and <strong>Expanded (AA, definite length)</strong> / <strong>Expanded (AE 80, indefinite length)</strong> wrap it in the TS 102 226 §5.2.1 scripting template (both execute on the live ISD; a template whose declared lengths do not match its body draws a Bad format TLV, which the operation shows as the step failure). The listing and registry queries follow that format: compact listings keep the chained GET RESPONSE, while an expanded run sends the same P2=00 queries wrapped and unchained (the reference TCA loader’s form, Le=’00’ returns the listing directly), and a registry query the card refuses in the current format is retried in the other formats — the report names the one that served it.</p>
|
||||
<p class="mb-2">Delivers Remote Application Management operations as SCP80 secured packets via SMS-PP-DOWNLOAD ENVELOPE. The card must support SCP03 (AES or 3DES). A saved card preset from the <strong>Cards</strong> tab provides the SPI, keys, TAR, and counter (RAM uses the preset's <strong>ISD TAR</strong>, <code class="font-mono text-sm">000000</code> by default). The <strong>Keyset</strong> selector next to the preset selector picks the keyset whose keys and counter the operation uses. It is disabled until a card preset is selected. The start button stays disabled until a card preset and one of its keysets are selected. The <strong>Format</strong> selector pins the remote command format: <strong>Auto (detect)</strong> probes the read-only GET STATUS command compact and in both expanded codings and keeps the first that the card executes, <strong>Compact</strong> sends the bare C-APDU, and <strong>Expanded (AA, definite length)</strong> / <strong>Expanded (AE 80, indefinite length)</strong> wrap it in the TS 102 226 §5.2.1 scripting template (both execute on the live ISD; a template whose declared lengths do not match its body draws a Bad format TLV, which the operation shows as the step failure). The listing and registry queries follow that format: compact listings keep the chained GET RESPONSE, while an expanded run sends the same P2=00 queries wrapped and unchained (the reference TCA loader’s form, Le=’00’ returns the listing directly), and a registry query the card refuses in the current format is retried in the other formats — the report names the one that served it. The card-resources block also names the card’s inferred <strong>ETSI release</strong> (<code class="font-mono text-sm">uicc.toolkit</code> version → REL-N, the same mapping as the CAP analysis; shown only when the expanded registry returned the versions).</p>
|
||||
|
||||
<h4 id="ram-operations" class="font-medium mb-1">Operations</h4>
|
||||
<table class="w-full text-sm mb-3 border-collapse">
|
||||
|
||||
@@ -8965,6 +8965,19 @@ const JC_AID_RIDS = {
|
||||
'A000000151': 'GlobalPlatform RID',
|
||||
};
|
||||
|
||||
// The card's ETSI release, inferred from its installed uicc.toolkit package
|
||||
// version - the same table the CAP analysis uses (capmem.ETSI_TOOLKIT_RELEASE,
|
||||
// drift-guarded by tests/test_cap_memory.py; the basis is the workspace
|
||||
// sim-tools corpus: the REL-7/REL-12 prepared trees and the TS 102 241
|
||||
// package-version table). Only uicc.toolkit has a documented mapping -
|
||||
// SIM-only cards get no label, the same rule as SIM-only CAPs.
|
||||
const UICC_TOOLKIT_AID = 'A0000000090005FFFFFFFF8912000000';
|
||||
const ETSI_TOOLKIT_RELEASE = {
|
||||
'1.0': 'REL-6/7', '1.1': 'REL-7', '1.2': 'REL-7', '1.3': 'REL-8',
|
||||
'1.4': 'REL-7', '1.5': 'REL-9', '1.11': 'REL-12',
|
||||
'1.12': 'REL-15/16/17', '1.13': 'REL-17', '1.14': 'REL-17/18',
|
||||
};
|
||||
|
||||
// Normalise an AID (strip spacing, uppercase) and reject malformed input;
|
||||
// '' when it cannot be an AID (5-16 bytes).
|
||||
function jcAidNorm(aid) {
|
||||
@@ -9436,6 +9449,20 @@ function ramElfVersionHint(elfForm) {
|
||||
return t('Package versions come from the expanded registry query \u2014 this card did not return them.');
|
||||
}
|
||||
|
||||
// The card's ETSI release from its installed uicc.toolkit package (pure):
|
||||
// {release, basis} when the version maps, {release: '', basis} when the
|
||||
// version is outside the table, {release: '', basis: ''} when the package
|
||||
// carries no version (the compact listing has none - the expanded registry's
|
||||
// CE tag provides it), and null when the card has no uicc.toolkit package at
|
||||
// all (SIM-only cards get no label, the same rule as SIM-only CAPs).
|
||||
function cardEtsiRelease(elfs) {
|
||||
const tk = (elfs || []).find(e => (e.aid || '').toUpperCase() === UICC_TOOLKIT_AID);
|
||||
if (!tk) return null;
|
||||
const v = String(tk.version || '').trim();
|
||||
if (!v) return { release: '', basis: '' };
|
||||
return { release: ETSI_TOOLKIT_RELEASE[v] || '', basis: 'uicc.toolkit ' + v };
|
||||
}
|
||||
|
||||
// The applet instance that belongs to a package (pure). The package's module
|
||||
// AIDs are the applet instance AIDs - they can differ from the package AID in
|
||||
// the last byte (live 2026-09-30: package A113...0200 with modules ...0208 /
|
||||
@@ -9479,6 +9506,15 @@ function ramRenderExploreHtml(mem, isd, apps, elfs, fmt, expanded, elfForm) {
|
||||
html += '<div>' + esc(t('Applications:')) + ' ' + (mem.appCount != null ? mem.appCount : '?') + '</div>';
|
||||
html += '<div>' + esc(t('Free NV:')) + ' ' + (mem.freeNV != null ? mem.freeNV + ' B' : '?') + '</div>';
|
||||
html += '<div>' + esc(t('Free Volatile:')) + ' ' + (mem.freeV != null ? mem.freeV + ' B' : '?') + '</div>';
|
||||
const etsi = cardEtsiRelease(elfs);
|
||||
if (etsi) {
|
||||
let val;
|
||||
if (etsi.release) val = etsi.release + (etsi.basis ? ' (' + etsi.basis + ')' : '');
|
||||
else if (etsi.basis) val = t('unknown') + ' (' + etsi.basis + ')';
|
||||
else val = t('unknown') + ' \u2014 ' + ramElfVersionHint(elfForm);
|
||||
html += '<div title="' + esc(t('Inferred from the card\'s installed uicc.toolkit package version (the same table as the CAP analysis); the expanded registry query provides the versions.')) + '">' +
|
||||
esc(t('ETSI release:')) + ' ' + esc(val) + '</div>';
|
||||
}
|
||||
if (fmt) {
|
||||
html += '<div title="' + esc(t('compact = the TS 102 226 5.2.1 command string (one C-APDU per secured packet, e.g. GET STATUS + GET RESPONSE); expanded = the AA/22 Command Scripting template (5.2.1.0). Detected per operation with a read-only GET STATUS probe; a card that rejects the deprecated compact P2.b2=0 form answers the expanded one.')) + '">' +
|
||||
esc(t('RAM command format:')) + ' ' + esc(fmt) + '</div>';
|
||||
@@ -19603,6 +19639,9 @@ const LANG_RU = {
|
||||
'Executable Module AIDs / Applet Class AIDs:': 'AID исполняемых модулей / классов апплетов:',
|
||||
'Applications:': 'Приложения:',
|
||||
'Free NV:': 'Свободно NV:',
|
||||
'ETSI release:': 'Релиз ETSI:',
|
||||
'unknown': 'неизвестно',
|
||||
'Inferred from the card\'s installed uicc.toolkit package version (the same table as the CAP analysis); the expanded registry query provides the versions.': 'Определяется по версии установленного на карте пакета uicc.toolkit (та же таблица, что и в анализе CAP); версии даёт расширенный запрос реестра.',
|
||||
'Free Volatile:': 'Свободно volatile:',
|
||||
'AID:': 'AID:',
|
||||
'Lifecycle:': 'Жизненный цикл:',
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
const CACHE = 'simple-v353';
|
||||
const CACHE = 'simple-v354';
|
||||
const URLS = [
|
||||
'index.html',
|
||||
'help.html',
|
||||
|
||||
@@ -26,7 +26,7 @@ const FNS = ['berLenStr', 'buildApdu', 'escHtml', 'esc', 'chainInit', 'chainRamB
|
||||
'ramFormatIsExpanded', 'ramFmtLadder',
|
||||
'stkParamsBuild', 'ramRemoteSwOk', 'spPorAccepted', 'ramIncrementCntr', 'ramDeleteFromExplorer', 'ramListingSpi2', 'ramRemoveFromExplorer', 'ramHasInstance', 'ramExpandedQueryApdu',
|
||||
'_parseRawElfEntry', '_parseRawAppEntry', 'ramParseElfStatus', 'ramParseAppStatus', 'ramParseModuleAids', 'parseTLV', '_parseE3Entry', '_parseMenuEntries',
|
||||
'ramExpandedReport', 'ramExpandedMore', 'ramExpandedTags', 'ramExpandedGroups', 'ramExpandedElfForm', 'ramElfVersionHint', 'ramChainGetResponse', 'ramDeriveElfVersions', 'ramElfAppletCandidate',
|
||||
'ramExpandedReport', 'ramExpandedMore', 'ramExpandedTags', 'ramExpandedGroups', 'ramExpandedElfForm', 'ramElfVersionHint', 'cardEtsiRelease', 'ramChainGetResponse', 'ramDeriveElfVersions', 'ramElfAppletCandidate',
|
||||
'spCntrLow', 'ramCntrLowHtml', 'ramCntrLowPresetIdx', 'ramShowCntrLow',
|
||||
'spKeysetKvnOf', 'spKeysetList', 'spKeysetFor', 'spKeysetCheck',
|
||||
'spKeysetOptionsHtml', 'spPresetIdx', 'tarPresetIdx',
|
||||
@@ -50,6 +50,10 @@ const an = html.match(/const JC_AID_NAMES = \{[\s\S]*?\n\};/);
|
||||
if (an) code += an[0].replace(/^const /, 'var ') + '\n';
|
||||
const ar = html.match(/const JC_AID_RIDS = \{[\s\S]*?\n\};/);
|
||||
if (ar) code += ar[0].replace(/^const /, 'var ') + '\n';
|
||||
const et = html.match(/const ETSI_TOOLKIT_RELEASE = \{[\s\S]*?\n\};/);
|
||||
if (et) code += et[0].replace(/^const /, 'var ') + '\n';
|
||||
const ua = html.match(/const UICC_TOOLKIT_AID = '[^']*';/);
|
||||
if (ua) code += ua[0].replace(/^const /, 'var ') + '\n';
|
||||
globalThis.spPresetWarningRender = () => {};
|
||||
globalThis.pysimApplyAvailability = () => {};
|
||||
eval(code);
|
||||
@@ -701,6 +705,36 @@ test('ramFmtLadder orders the registry command formats cautiously', () => {
|
||||
assert.deepStrictEqual(ramFmtLadder('expanded-ae', 'expanded-ae'), ['expanded-ae', 'expanded', 'compact']);
|
||||
});
|
||||
|
||||
test('cardEtsiRelease infers the card ETSI release from uicc.toolkit', () => {
|
||||
const tk = v => ({ aid: 'A0000000090005FFFFFFFF8912000000', version: v });
|
||||
assert.deepStrictEqual(cardEtsiRelease([tk('1.12')]),
|
||||
{ release: 'REL-15/16/17', basis: 'uicc.toolkit 1.12' });
|
||||
assert.deepStrictEqual(cardEtsiRelease([tk('1.11')]),
|
||||
{ release: 'REL-12', basis: 'uicc.toolkit 1.11' });
|
||||
assert.deepStrictEqual(cardEtsiRelease([tk('1.5')]),
|
||||
{ release: 'REL-9', basis: 'uicc.toolkit 1.5' });
|
||||
// a version outside the table: the basis is still named
|
||||
assert.deepStrictEqual(cardEtsiRelease([tk('9.9')]),
|
||||
{ release: '', basis: 'uicc.toolkit 9.9' });
|
||||
// the compact listing carries no versions (the expanded CE tag does)
|
||||
assert.deepStrictEqual(cardEtsiRelease([tk('')]), { release: '', basis: '' });
|
||||
// no uicc.toolkit (SIM-only card): no label, like SIM-only CAPs
|
||||
assert.strictEqual(cardEtsiRelease([{ aid: 'A0000000090003FFFFFFFF8910710002', version: '2.6' }]), null);
|
||||
assert.strictEqual(cardEtsiRelease(null), null);
|
||||
});
|
||||
|
||||
test('the explorer shows the inferred ETSI release', () => {
|
||||
globalThis.t = s => s;
|
||||
const out = ramRenderExploreHtml({ appCount: 1, freeNV: 2, freeV: 3 }, [], [],
|
||||
[{ aid: 'A0000000090005FFFFFFFF8912000000', version: '1.12' }], 'compact', '', '');
|
||||
delete globalThis.t;
|
||||
assert.ok(out.includes('ETSI release: REL-15/16/17 (uicc.toolkit 1.12)'), out);
|
||||
globalThis.t = s => s;
|
||||
const none = ramRenderExploreHtml({ appCount: 1, freeNV: 2, freeV: 3 }, [], [], [], 'compact', '', '');
|
||||
delete globalThis.t;
|
||||
assert.ok(!none.includes('ETSI release:'), none);
|
||||
});
|
||||
|
||||
test('ramExpandedReport names the expanded serving format', () => {
|
||||
globalThis.t = s => s;
|
||||
const out = ramExpandedReport([
|
||||
|
||||
@@ -407,6 +407,20 @@ class TestCapAnalyzer(unittest.TestCase):
|
||||
pwa = dict(re.findall(r"'([\d.]+)':\s*'([^']+)'", m.group(1)))
|
||||
self.assertEqual(pwa, capmem.JC_FRAMEWORK_JDK)
|
||||
|
||||
def test_etsi_table_matches_the_pwa(self):
|
||||
# the PWA infers the card's ETSI release from its installed
|
||||
# uicc.toolkit version with the same table as the CAP analysis
|
||||
# (drift guard, like JC_FRAMEWORK_SDK above)
|
||||
html = pathlib.Path(__file__).resolve().parents[1].joinpath(
|
||||
'frontend', 'index.html').read_text(encoding='utf-8')
|
||||
m = re.search(r"const ETSI_TOOLKIT_RELEASE = \{(.*?)\};", html, re.S)
|
||||
self.assertIsNotNone(m, 'ETSI_TOOLKIT_RELEASE not found in index.html')
|
||||
pwa = dict(re.findall(r"'([\d.]+)':\s*'([^']+)'", m.group(1)))
|
||||
self.assertEqual(pwa, capmem.ETSI_TOOLKIT_RELEASE)
|
||||
a = re.search(r"const UICC_TOOLKIT_AID = '([0-9A-F]+)';", html)
|
||||
self.assertIsNotNone(a, 'UICC_TOOLKIT_AID not found in index.html')
|
||||
self.assertEqual(a.group(1), capmem.UICC_TOOLKIT_AID)
|
||||
|
||||
def test_platform_requirement_from_the_framework_import(self):
|
||||
# java.lang only: no Java Card level named, but the int flag shows
|
||||
report, _ = capmem.analyze_bytes(
|
||||
|
||||
Reference in New Issue
Block a user