feat: name standard package AIDs in the Explore / SCP81 / R-APDU views (v3.5.7)
The RAM Explore view, the SCP81 GET STATUS script results and the R-APDU parser tree showed package AIDs as bare hex. A shared resolver now annotates the known standard JavaCard / ETSI / 3GPP / GlobalPlatform package AIDs with their library name (workspace export-file study `docs/JAVACARD.md`, 2026-09-26, plus the GP default ISD AID from GP Card Spec v2.3.1 H.1.3); a RID table gives a weak owner hint for otherwise unknown AIDs, and vendor/applet AIDs stay bare. - JC_AID_NAMES / JC_AID_RIDS + jcAidName / jcAidSuffix / jcAidHtml. - Wired into ramRenderExploreHtml (ISD, applications, ELFs, module and SD AIDs), scp81ResultLines GET STATUS listings and decodeTlvValue (4F/84/C4/CC - the R-APDU parser tree). - aid_names.test.js (families, normalisation, RID hints, malformed input, table sanity) plus render assertions in ram.test.js and scp81.test.js. - Help EN/RU note the annotation; table is hand-maintained from the note. 561 frontend / 421 Python green; version 3.5.7; sw cache simple-v260.
This commit is contained in:
@@ -271,6 +271,7 @@
|
||||
<li><strong>Декодирование SW</strong> — статусные слова разрешаются по generic-, UICC- (TS 102 221) и GlobalPlatform-таблицам, контекст определяется автоматически.</li>
|
||||
<li><strong>Декодирование привилегий</strong> — ответы GET DATA / INSTALL декодируют байты привилегий в читаемые флаги.</li>
|
||||
<li><strong>Данные ответа</strong> — raw hex отображается и интерпретируется согласно команде (например, FCP-шаблоны SELECT).</li>
|
||||
<li><strong>Имена AID</strong> — теги с AID (<code class="font-mono text-sm">4F</code>, <code class="font-mono text-sm">84</code>, <code class="font-mono text-sm">C4</code>, <code class="font-mono text-sm">CC</code>) дополняются именем стандартного библиотечного пакета, когда AID — известный пакет JavaCard / ETSI / 3GPP / GlobalPlatform (или подсказкой по RID).</li>
|
||||
</ul>
|
||||
|
||||
|
||||
@@ -329,6 +330,7 @@
|
||||
<li><strong>Приложения</strong> — AID, жизненный цикл, привилегии, связанный ELF/SD. Каждое имеет кнопку <strong>Удалить</strong> (GP <code class="font-mono text-sm">DELETE</code> по AID).</li>
|
||||
<li><strong>Исполняемые файлы загрузки (ELF) / пакеты</strong> — AID, жизненный цикл, версии, AID модулей. Каждый имеет <strong>Удалить</strong> (только ELF) и <strong>Удалить все</strong> (каскадное: ELF + модули + установленные приложения, P2=<code class="font-mono text-sm">0x80</code>).</li>
|
||||
</ul>
|
||||
<p class="text-sm mb-3">Стандартные пакетные AID дополняются именем библиотеки (пакеты JavaCard / ETSI / 3GPP / GlobalPlatform, а также AID ISD по умолчанию); неизвестные и вендорские AID остаются без имени.</p>
|
||||
<p class="text-sm mb-3">Удаление подтверждается через диалог браузера перед отправкой команды GP <code class="font-mono text-sm">DELETE</code> через SCP80. Обзор автоматически обновляется после успешного удаления.</p>
|
||||
|
||||
|
||||
@@ -368,7 +370,7 @@
|
||||
</ul>
|
||||
<p class="mb-2">Блок <strong>Настройки</strong> (по умолчанию свёрнут; при отличии от эталонных значений показывается метка <em>изменены</em>; применяются при <strong>Start</strong>, сохраняются в браузере) открывает HTTP-фрейминг — <strong>Chunked body</strong> / <strong>размер чанка</strong> (0 = весь ответ в одной TLS-записи), заголовок Connection, компактные заголовки (без необязательного пробела после <code class="font-mono text-sm">:</code>, допустимо по RFC 7230 §3.2) и Next-URI (флажок + шаблон; выкл - команда без ответа: карта выполнит команду, не вернёт строку ответа и закроет сессию) — и фрейминг скрипта (неопределённая длина <code class="font-mono text-sm">AE 80 …</code> / определённая <code class="font-mono text-sm">AA</code> в Command Scripting template, теги comprehension-required и необязательный <code class="font-mono text-sm">X-Admin-Targeted-Application</code> со своим флажком). <strong>Показывать события канала</strong> (ENVELOPE Channel status, TS 102 223 §7.5.11) действует во всех режимах. Соединение удерживается между POST-запросами и аккуратно закрывается на 204. У TLS нет настроек: слушатель предлагает TLS 1.0–1.2 и все PSK-наборы, а карта выбирает; строка состояния и TLS-журнал показывают фактически использованные версию и набор, а сбой рукопожатия по причине TLS/набора пишется как <code class="font-mono text-sm">tls-handshake-failed</code>.</p>
|
||||
<p class="mb-2"><strong>Сценарий</strong> выбирает список команд, отдаваемый в сессии — <strong>Нет</strong> (оставить настроенный на сервере скрипт) или один из скриптов подвкладки <strong>«Скрипты»</strong>; выбранный список передаётся серверу при старте слушателя.</p>
|
||||
<p class="text-sm mb-3">Строка состояния показывает слушатель, согласованный идентификатор и активные каналы (байты in/out). Панель <strong>Script results (R-APDUs)</strong> перечисляет каждый отданный C-APDU с его R-APDU/SW и прогрессом <em>done/total</em>; журнал <strong>HTTP OTA log</strong> фиксирует OPEN/CLOSE CHANNEL, SEND/RECEIVE DATA и каждый шаг TLS/HTTP/скрипта (<code class="font-mono text-sm">tls-handshake</code>, <code class="font-mono text-sm">tls-request</code>, <code class="font-mono text-sm">script-send</code>, <code class="font-mono text-sm">script-rapdu</code>, <code class="font-mono text-sm">script-page</code>, <code class="font-mono text-sm">script-done</code>, <code class="font-mono text-sm">data-available</code>, <code class="font-mono text-sm">peer-close</code>), кнопка <strong>Clear</strong> очищает его.</p>
|
||||
<p class="text-sm mb-3">Строка состояния показывает слушатель, согласованный идентификатор и активные каналы (байты in/out). Панель <strong>Script results (R-APDUs)</strong> перечисляет каждый отданный C-APDU с его R-APDU/SW и прогрессом <em>done/total</em> (списки GET STATUS дополняют известные стандартные пакетные AID именем библиотеки); журнал <strong>HTTP OTA log</strong> фиксирует OPEN/CLOSE CHANNEL, SEND/RECEIVE DATA и каждый шаг TLS/HTTP/скрипта (<code class="font-mono text-sm">tls-handshake</code>, <code class="font-mono text-sm">tls-request</code>, <code class="font-mono text-sm">script-send</code>, <code class="font-mono text-sm">script-rapdu</code>, <code class="font-mono text-sm">script-page</code>, <code class="font-mono text-sm">script-done</code>, <code class="font-mono text-sm">data-available</code>, <code class="font-mono text-sm">peer-close</code>), кнопка <strong>Clear</strong> очищает его.</p>
|
||||
<h3 id="scp81-scripts" class="text-lg font-medium mb-2">5.2 Скрипты</h3>
|
||||
<p class="text-sm mb-2">Именованные списки APDU хранятся локально (<code class="font-mono text-sm">simple_scripts</code>) и передаются серверу при старте слушателя. Каждый список — последовательность hex C-APDU (по одной в строке; допускаются комментарии <code class="font-mono text-sm">#</code>/<code class="font-mono text-sm">;</code>). Кнопка <strong>Новый</strong> создаёт список из шаблона:</p>
|
||||
<ul class="list-disc list-inside text-sm space-y-1 mb-3">
|
||||
|
||||
+3
-1
@@ -270,6 +270,7 @@
|
||||
<li><strong>SW decode</strong> — status words resolved against generic, UICC (TS 102 221), and GlobalPlatform maps, with context auto-detected.</li>
|
||||
<li><strong>Privilege decode</strong> — GET DATA / INSTALL response payloads decode the privilege bytes into human-readable flags.</li>
|
||||
<li><strong>Response data</strong> — raw hex rendered and interpreted per command (e.g. SELECT FCP templates).</li>
|
||||
<li><strong>AID names</strong> — AID-carrying tags (<code class="font-mono text-sm">4F</code>, <code class="font-mono text-sm">84</code>, <code class="font-mono text-sm">C4</code>, <code class="font-mono text-sm">CC</code>) are annotated with the standard library package name when the AID is a known JavaCard / ETSI / 3GPP / GlobalPlatform package (or a RID owner hint).</li>
|
||||
</ul>
|
||||
|
||||
|
||||
@@ -328,6 +329,7 @@
|
||||
<li><strong>Applications</strong> — AID, lifecycle, privileges, associated ELF/SD. Each has a <strong>Delete</strong> button (GP <code class="font-mono text-sm">DELETE</code> by AID).</li>
|
||||
<li><strong>Executable Load Files</strong> — AID, lifecycle, version, module AIDs. Each has <strong>Delete</strong> (ELF only) and <strong>Delete All</strong> (cascade: ELF + modules + installed Applications, P2=<code class="font-mono text-sm">0x80</code>) buttons.</li>
|
||||
</ul>
|
||||
<p class="text-sm mb-3">Standard package AIDs are annotated with their library name (JavaCard / ETSI / 3GPP / GlobalPlatform packages, plus the default GlobalPlatform ISD AID); unknown and vendor AIDs stay bare.</p>
|
||||
<p class="text-sm mb-3">Delete confirms via a browser prompt before sending the GP <code class="font-mono text-sm">DELETE</code> command via SCP80. The explorer auto-refreshes after a successful deletion.</p>
|
||||
|
||||
|
||||
@@ -367,7 +369,7 @@
|
||||
</ul>
|
||||
<p class="mb-2">The <strong>Options</strong> block (collapsed by default; a <em>custom</em> marker appears when anything differs from the reference defaults; applied at <strong>Start</strong>, remembered in the browser) exposes the HTTP framing — <strong>Chunked body</strong> / <strong>chunk size</strong> (0 = the whole response in one TLS record), the Connection header, compact headers (omitting the optional space after <code class="font-mono text-sm">:</code>, legal per RFC 7230 §3.2) and the Next-URI (checkbox + template; unchecked = one-shot: the card executes the command, returns no response string and closes the session) — and the script framing (indefinite <code class="font-mono text-sm">AE 80 …</code> / definite <code class="font-mono text-sm">AA</code> Command Scripting template, comprehension-required tags, and the optional <code class="font-mono text-sm">X-Admin-Targeted-Application</code> with its own checkbox). <strong>Show link events</strong> (Channel status ENVELOPEs, TS 102 223 §7.5.11) applies to every mode. The connection is kept open between POSTs and closed cleanly at the 204. TLS itself has no settings: the listener offers TLS 1.0–1.2 and all PSK suites and lets the card negotiate; the state line and the TLS log show the version/cipher actually used, and a handshake that fails for a TLS/cipher reason is logged as <code class="font-mono text-sm">tls-handshake-failed</code>.</p>
|
||||
<p class="mb-2"><strong>Script</strong> selects the command list served over the session — <strong>None</strong> (leave the server's configured script) or one of the scripts from the <strong>Scripts</strong> pill; the chosen list is sent to the server when the listener starts.</p>
|
||||
<p class="text-sm mb-3">The state line shows the listener, the negotiated identity and live channels (bytes in/out). <strong>Script results (R-APDUs)</strong> lists each served C-APDU with its R-APDU/SW and a <em>done/total</em> progress; the <strong>HTTP OTA log</strong> records OPEN/CLOSE CHANNEL, SEND/RECEIVE DATA and every TLS/HTTP/script step (<code class="font-mono text-sm">tls-handshake</code>, <code class="font-mono text-sm">tls-request</code>, <code class="font-mono text-sm">script-send</code>, <code class="font-mono text-sm">script-rapdu</code>, <code class="font-mono text-sm">script-page</code>, <code class="font-mono text-sm">script-done</code>, <code class="font-mono text-sm">data-available</code>, <code class="font-mono text-sm">peer-close</code>), with <strong>Clear</strong>.</p>
|
||||
<p class="text-sm mb-3">The state line shows the listener, the negotiated identity and live channels (bytes in/out). <strong>Script results (R-APDUs)</strong> lists each served C-APDU with its R-APDU/SW and a <em>done/total</em> progress (GET STATUS listings annotate known standard package AIDs with their library name); the <strong>HTTP OTA log</strong> records OPEN/CLOSE CHANNEL, SEND/RECEIVE DATA and every TLS/HTTP/script step (<code class="font-mono text-sm">tls-handshake</code>, <code class="font-mono text-sm">tls-request</code>, <code class="font-mono text-sm">script-send</code>, <code class="font-mono text-sm">script-rapdu</code>, <code class="font-mono text-sm">script-page</code>, <code class="font-mono text-sm">script-done</code>, <code class="font-mono text-sm">data-available</code>, <code class="font-mono text-sm">peer-close</code>), with <strong>Clear</strong>.</p>
|
||||
<h3 id="scp81-scripts" class="text-lg font-medium mb-2">5.2 Scripts</h3>
|
||||
<p class="text-sm mb-2">Named APDU lists stored locally (<code class="font-mono text-sm">simple_scripts</code>) and sent to the server when a listener starts. Each list is a sequence of hex C-APDUs (one per line; <code class="font-mono text-sm">#</code>/<code class="font-mono text-sm">;</code> comments allowed). <strong>New</strong> creates one from a template:</p>
|
||||
<ul class="list-disc list-inside text-sm space-y-1 mb-3">
|
||||
|
||||
+113
-17
@@ -1559,7 +1559,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.5.6';
|
||||
const SIMPLE_VERSION = '3.5.7';
|
||||
document.getElementById('app-version').textContent = 'v' + SIMPLE_VERSION;
|
||||
|
||||
// ===== Tab switching =====
|
||||
@@ -6221,11 +6221,11 @@ function decodeTlvValue(t) {
|
||||
if (t.tag === 'C5' && t.value.length >= 2) {
|
||||
return decodePrivileges(t.value);
|
||||
}
|
||||
if (t.tag === '4F') {
|
||||
return t.value.replace(/(..)/g, '$1 ').trim();
|
||||
}
|
||||
if (t.tag === 'C4' || t.tag === 'CC' || t.tag === '84') {
|
||||
return t.value.replace(/(..)/g, '$1 ').trim();
|
||||
if (t.tag === '4F' || t.tag === 'C4' || t.tag === 'CC' || t.tag === '84') {
|
||||
// AID-carrying tags: spaced hex plus the standard package name when known.
|
||||
const spaced = t.value.replace(/(..)/g, '$1 ').trim();
|
||||
const name = jcAidName(t.value);
|
||||
return name ? spaced + ' (' + name + ')' : spaced;
|
||||
}
|
||||
if (t.tag === 'CE' && t.value.length === 4) {
|
||||
return 'v' + parseInt(t.value.substr(0,2),16) + '.' + parseInt(t.value.substr(2,2),16);
|
||||
@@ -7147,6 +7147,101 @@ async function ramSendOta(apduHex, sp) {
|
||||
return await pysimFetch('/api/send-ota', body);
|
||||
}
|
||||
|
||||
// ===== Standard package AIDs (JavaCard / ETSI / 3GPP / GlobalPlatform) =====
|
||||
// Package AIDs of the standard libraries applets can link against, mapped to
|
||||
// the library package name. Source: the workspace research note
|
||||
// `docs/JAVACARD.md` (2026-09-26 - parsed from the JavaCard export files in
|
||||
// the project corpus: 59 unique package AIDs; its standard-library subset plus
|
||||
// the recognisable non-library entries of its section 5), and GP Card Spec
|
||||
// v2.3.1 H.1.3 for the default Issuer Security Domain AID. Matching is exact;
|
||||
// JC_AID_RIDS only gives a weak owner hint for otherwise unknown AIDs
|
||||
// (JAVACARD.md section 6). Hand-maintained - update from the note, never from
|
||||
// a card registry (vendor/applet AIDs stay unnamed).
|
||||
const JC_AID_NAMES = {
|
||||
// Oracle / Sun JavaCard (RID A000000062)
|
||||
'A0000000620001': 'java.lang',
|
||||
'A0000000620002': 'java.io',
|
||||
'A0000000620003': 'java.rmi',
|
||||
'A0000000620101': 'javacard.framework',
|
||||
'A000000062010101': 'javacard.framework.service',
|
||||
'A0000000620102': 'javacard.security',
|
||||
'A0000000620201': 'javacardx.crypto',
|
||||
'A0000000620202': 'javacardx.biometry',
|
||||
'A0000000620203': 'javacardx.external',
|
||||
'A0000000620204': 'javacardx.biometry1toN',
|
||||
'A0000000620205': 'javacardx.security',
|
||||
'A000000062020501': 'javacardx.security.cert',
|
||||
'A000000062020502': 'javacardx.security.derivation',
|
||||
'A000000062020503': 'javacardx.security.util',
|
||||
'A000000062020801': 'javacardx.framework.util',
|
||||
'A00000006202080101': 'javacardx.framework.util.intx',
|
||||
'A000000062020802': 'javacardx.framework.math',
|
||||
'A000000062020803': 'javacardx.framework.tlv',
|
||||
'A000000062020804': 'javacardx.framework.string',
|
||||
'A000000062020805': 'javacardx.framework.event',
|
||||
'A000000062020806': 'javacardx.framework.nio',
|
||||
'A000000062020807': 'javacardx.framework.time',
|
||||
'A0000000620209': 'javacardx.apdu',
|
||||
'A000000062020901': 'javacardx.apdu.util',
|
||||
// JavaCard runtime internals (recognisable, not linkable libraries)
|
||||
'A0000000620301': 'com.sun.javacard.impl',
|
||||
'A000000062030108': 'com.sun.javacard.installer',
|
||||
'A00000006203010E': 'com.sun.javacard.implBiometry',
|
||||
'A00000006203010F': 'com.sun.javacard.crypto',
|
||||
// ETSI SIM / UICC (RID A000000009)
|
||||
'A0000000090003FFFFFFFF8910710001': 'sim.access',
|
||||
'A0000000090003FFFFFFFF8910710002': 'sim.toolkit',
|
||||
'A0000000090005FFFFFFFF8911000000': 'uicc.access',
|
||||
'A0000000090005FFFFFFFF8911010000': 'uicc.access.fileadministration',
|
||||
'A0000000090005FFFFFFFF8911020000': 'uicc.access.bertlvfile',
|
||||
'A0000000090005FFFFFFFF8912000000': 'uicc.toolkit',
|
||||
'A0000000090005FFFFFFFF8913000000': 'uicc.system',
|
||||
'A0000000090005FFFFFFFF8917000000': 'uicc.suspendresume',
|
||||
'A0000000090005FFFFFFFF8918010000': 'uicc.services.highupdatearray',
|
||||
// 3GPP USIM / ISIM (RID A000000087)
|
||||
'A0000000871005FFFFFFFF8913100000': 'uicc.usim.access',
|
||||
'A0000000871005FFFFFFFF8913200000': 'uicc.usim.toolkit',
|
||||
'A0000000871005FFFFFFFF8913300000': 'uicc.usim.geolocation',
|
||||
'A0000000871005FFFFFFFF8913400000': 'uicc.usim.suci',
|
||||
'A0000000871005FFFFFFFF8914100000': 'uicc.isim.access',
|
||||
// GlobalPlatform
|
||||
'A00000015100': 'org.globalplatform',
|
||||
'A000000151000000': 'GlobalPlatform Issuer Security Domain', // default ISD AID, GPC v2.3.1 H.1.3
|
||||
// Legacy / test packages (recognisable, not linkable libraries)
|
||||
'A0000000030000': 'visa/openplatform',
|
||||
'123456654011': 'com.denis',
|
||||
};
|
||||
const JC_AID_RIDS = {
|
||||
'A000000062': 'Oracle JavaCard RID',
|
||||
'A000000009': 'ETSI RID',
|
||||
'A000000087': '3GPP RID',
|
||||
'A000000151': 'GlobalPlatform RID',
|
||||
};
|
||||
|
||||
// Resolve an AID (any hex case/spacing) to a standard package name, a
|
||||
// well-known AID name or a RID owner hint; '' when nothing is known.
|
||||
function jcAidName(aid) {
|
||||
const s = (aid || '').replace(/[^0-9a-fA-F]/g, '').toUpperCase();
|
||||
if (s.length < 10 || s.length > 32 || s.length % 2) return '';
|
||||
if (JC_AID_NAMES[s]) return JC_AID_NAMES[s];
|
||||
for (const rid in JC_AID_RIDS) {
|
||||
if (s.startsWith(rid)) return JC_AID_RIDS[rid];
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
// ' (name)' for plain-text listings; '' when the AID is unknown.
|
||||
function jcAidSuffix(aid) {
|
||||
const name = jcAidName(aid);
|
||||
return name ? ' (' + name + ')' : '';
|
||||
}
|
||||
|
||||
// Escaped AID plus a muted parenthesised name, for HTML listings.
|
||||
function jcAidHtml(aid) {
|
||||
const name = jcAidName(aid);
|
||||
return esc(aid) + (name ? ' <span class="text-gray-400 dark:text-slate-500">(' + esc(name) + ')</span>' : '');
|
||||
}
|
||||
|
||||
// ===== TLV parsers for GET STATUS responses (P2=02 format) =====
|
||||
// P2=02 wraps each entry in an 'E3' GP Registry Data template containing TLV tags.
|
||||
// Fallback: if the card only supports P2=00 (raw format), raw parsers are used.
|
||||
@@ -7359,10 +7454,10 @@ function ramRenderExploreHtml(mem, isd, apps, elfs) {
|
||||
html += '<div class="font-semibold text-sm mb-1">' + esc(t('ISD (Issuer Security Domain)')) + '</div>';
|
||||
isd.forEach(o => {
|
||||
html += '<div class="mb-1 pl-2 border-l-2 border-blue-400">';
|
||||
html += '<div>' + esc(t('AID:')) + ' ' + (o.aid || '?') + '</div>';
|
||||
html += '<div>' + esc(t('AID:')) + ' ' + (o.aid ? jcAidHtml(o.aid) : '?') + '</div>';
|
||||
html += '<div>' + esc(t('Lifecycle:')) + ' ' + ramFmtLifecycle(o.lifecycle || '') + '</div>';
|
||||
if (o.privileges) html += '<div>' + esc(t('Privileges:')) + ' ' + ramFmtPrivileges(o.privileges) + ' (' + o.privileges + ')</div>';
|
||||
if (o.sdAid) html += '<div>' + esc(t('SD AID:')) + ' ' + o.sdAid + '</div>';
|
||||
if (o.sdAid) html += '<div>' + esc(t('SD AID:')) + ' ' + jcAidHtml(o.sdAid) + '</div>';
|
||||
html += '</div>';
|
||||
});
|
||||
html += '</div>';
|
||||
@@ -7372,7 +7467,7 @@ function ramRenderExploreHtml(mem, isd, apps, elfs) {
|
||||
html += '<div class="font-semibold text-sm mb-1">' + esc(t('Applications / Applet Instances')) + '</div>';
|
||||
apps.forEach(o => {
|
||||
html += '<div class="mb-1 pl-2 border-l-2 border-green-400">';
|
||||
html += '<div>' + esc(t('Application / Instance AID:')) + ' ' + (o.aid || '?');
|
||||
html += '<div>' + esc(t('Application / Instance AID:')) + ' ' + (o.aid ? jcAidHtml(o.aid) : '?');
|
||||
if (o.aid) {
|
||||
html += ' <button onclick="ramDeleteFromExplorer(\'' + o.aid + '\', false)" data-needs="card" class="ml-2 px-2 py-0.5 text-xs bg-red-100 text-red-700 rounded hover:bg-red-200 dark:bg-red-900/30 dark:text-red-400 disabled:opacity-40 disabled:cursor-not-allowed">' + esc(t('Delete')) + '</button>';
|
||||
}
|
||||
@@ -7380,8 +7475,8 @@ function ramRenderExploreHtml(mem, isd, apps, elfs) {
|
||||
html += '<div>' + esc(t('Lifecycle:')) + ' ' + ramFmtLifecycle(o.lifecycle || '') + '</div>';
|
||||
if (o.privileges) html += '<div>' + esc(t('Privileges:')) + ' ' + ramFmtPrivileges(o.privileges) + ' (' + o.privileges + ')</div>';
|
||||
if (o.implicitSel) html += '<div>' + esc(t('Implicit sel:')) + ' ' + o.implicitSel + '</div>';
|
||||
if (o.elfAid) html += '<div>' + esc(t('Load File AID / Package AID:')) + ' ' + o.elfAid + '</div>';
|
||||
if (o.sdAid) html += '<div>' + esc(t('SD AID:')) + ' ' + o.sdAid + '</div>';
|
||||
if (o.elfAid) html += '<div>' + esc(t('Load File AID / Package AID:')) + ' ' + jcAidHtml(o.elfAid) + '</div>';
|
||||
if (o.sdAid) html += '<div>' + esc(t('SD AID:')) + ' ' + jcAidHtml(o.sdAid) + '</div>';
|
||||
html += '</div>';
|
||||
});
|
||||
html += '</div>';
|
||||
@@ -7391,7 +7486,7 @@ function ramRenderExploreHtml(mem, isd, apps, elfs) {
|
||||
html += '<div class="font-semibold text-sm mb-1">' + esc(t('Executable Load Files (ELFs) / Packages')) + '</div>';
|
||||
elfs.forEach(o => {
|
||||
html += '<div class="mb-1 pl-2 border-l-2 border-purple-400">';
|
||||
html += '<div>' + esc(t('Load File AID / Package AID:')) + ' ' + (o.aid || '?');
|
||||
html += '<div>' + esc(t('Load File AID / Package AID:')) + ' ' + (o.aid ? jcAidHtml(o.aid) : '?');
|
||||
if (o.aid) {
|
||||
html += ' <button onclick="ramDeleteFromExplorer(\'' + o.aid + '\', false)" data-needs="card" class="ml-2 px-2 py-0.5 text-xs bg-red-100 text-red-700 rounded hover:bg-red-200 dark:bg-red-900/30 dark:text-red-400 disabled:opacity-40 disabled:cursor-not-allowed">' + esc(t('Delete')) + '</button>';
|
||||
html += ' <button onclick="ramDeleteFromExplorer(\'' + o.aid + '\', true)" data-needs="card" class="ml-1 px-2 py-0.5 text-xs bg-red-100 text-red-700 rounded hover:bg-red-200 dark:bg-red-900/30 dark:text-red-400 disabled:opacity-40 disabled:cursor-not-allowed">' + esc(t('Delete All')) + '</button>';
|
||||
@@ -7401,9 +7496,9 @@ function ramRenderExploreHtml(mem, isd, apps, elfs) {
|
||||
if (o.version) html += '<div>' + esc(t('Version:')) + ' ' + o.version + '</div>';
|
||||
if (o.moduleAids && o.moduleAids.length) {
|
||||
html += '<div>' + esc(t('Executable Module AIDs / Applet Class AIDs:')) + '</div>';
|
||||
o.moduleAids.forEach(m => { html += '<div class="pl-4">- ' + m + '</div>'; });
|
||||
o.moduleAids.forEach(m => { html += '<div class="pl-4">- ' + jcAidHtml(m) + '</div>'; });
|
||||
}
|
||||
if (o.sdAid) html += '<div>' + esc(t('SD AID:')) + ' ' + o.sdAid + '</div>';
|
||||
if (o.sdAid) html += '<div>' + esc(t('SD AID:')) + ' ' + jcAidHtml(o.sdAid) + '</div>';
|
||||
html += '</div>';
|
||||
});
|
||||
html += '</div>';
|
||||
@@ -10391,9 +10486,10 @@ function scp81ResultLines(group) {
|
||||
});
|
||||
unique.forEach(e => {
|
||||
const priv = (typeof decodePrivileges === 'function' && e.privileges) ? decodePrivileges(e.privileges) : (e.privileges || '');
|
||||
lines.push(e.aid + (e.lifecycle ? ' life=' + e.lifecycle : '') + (priv ? ' [' + priv + ']' : '') +
|
||||
(e.elf ? ' elf=' + e.elf : '') + (e.modules.length ? ' module=' + e.modules.join(',') : '') +
|
||||
(e.sd ? ' sd=' + e.sd : ''));
|
||||
lines.push(e.aid + jcAidSuffix(e.aid) + (e.lifecycle ? ' life=' + e.lifecycle : '') + (priv ? ' [' + priv + ']' : '') +
|
||||
(e.elf ? ' elf=' + e.elf + jcAidSuffix(e.elf) : '') +
|
||||
(e.modules.length ? ' module=' + e.modules.map(m => m + jcAidSuffix(m)).join(',') : '') +
|
||||
(e.sd ? ' sd=' + e.sd + jcAidSuffix(e.sd) : ''));
|
||||
});
|
||||
const bad = group.results.filter(r => r.sw && r.sw !== '9000' && r.sw !== 'CAFE');
|
||||
bad.forEach(r => lines.push('SW ' + r.sw));
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
const CACHE = 'simple-v259';
|
||||
const CACHE = 'simple-v260';
|
||||
const URLS = [
|
||||
'index.html',
|
||||
'help.html',
|
||||
|
||||
@@ -0,0 +1,93 @@
|
||||
const { test } = require('node:test');
|
||||
const assert = require('node:assert');
|
||||
const fs = require('node:fs');
|
||||
const path = require('node:path');
|
||||
|
||||
const html = fs.readFileSync(path.join(__dirname, '..', 'index.html'), 'utf8');
|
||||
|
||||
function extractBlock(startMarker, endMarker) {
|
||||
const start = html.indexOf(startMarker);
|
||||
const end = html.indexOf(endMarker, start);
|
||||
if (start < 0 || end < 0) throw new Error('block not found: ' + startMarker);
|
||||
return html.slice(start, end);
|
||||
}
|
||||
|
||||
function extractFunc(src, name) {
|
||||
const re = new RegExp('function\\s+' + name + '\\s*\\([^)]*\\)\\s*\\{');
|
||||
const m = re.exec(src);
|
||||
if (!m) throw new Error('function ' + name + ' not found');
|
||||
let i = m.index + m[0].length - 1;
|
||||
let depth = 0;
|
||||
for (; i < src.length; i++) {
|
||||
if (src[i] === '{') depth++;
|
||||
else if (src[i] === '}') {
|
||||
depth--;
|
||||
if (depth === 0) break;
|
||||
}
|
||||
}
|
||||
return src.slice(m.index, i + 1);
|
||||
}
|
||||
|
||||
// Rewrite top-level const -> var so the tables leak out of sloppy-mode eval.
|
||||
eval(extractBlock('const JC_AID_NAMES = {', 'const JC_AID_RIDS = {').replace(/^const /gm, 'var '));
|
||||
eval(extractBlock('const JC_AID_RIDS = {', 'function jcAidName').replace(/^const /gm, 'var '));
|
||||
eval(extractFunc(html, 'jcAidName'));
|
||||
eval(extractFunc(html, 'jcAidSuffix'));
|
||||
eval(extractFunc(html, 'jcAidHtml'));
|
||||
globalThis.esc = s => s;
|
||||
|
||||
test('the table resolves each standard library family', () => {
|
||||
// Oracle / JavaCard, ETSI (SIM and UICC era), 3GPP, GlobalPlatform
|
||||
assert.strictEqual(jcAidName('A0000000620101'), 'javacard.framework');
|
||||
assert.strictEqual(jcAidName('A000000062010101'), 'javacard.framework.service');
|
||||
assert.strictEqual(jcAidName('A0000000090003FFFFFFFF8910710002'), 'sim.toolkit');
|
||||
assert.strictEqual(jcAidName('A0000000090005FFFFFFFF8912000000'), 'uicc.toolkit');
|
||||
assert.strictEqual(jcAidName('A0000000871005FFFFFFFF8913200000'), 'uicc.usim.toolkit');
|
||||
assert.strictEqual(jcAidName('A00000015100'), 'org.globalplatform');
|
||||
// JAVACARD.md section 5: recognisable, not linkable libraries
|
||||
assert.strictEqual(jcAidName('A00000006203010F'), 'com.sun.javacard.crypto');
|
||||
assert.strictEqual(jcAidName('A0000000030000'), 'visa/openplatform');
|
||||
assert.strictEqual(jcAidName('123456654011'), 'com.denis');
|
||||
});
|
||||
|
||||
test('the default ISD resolves and unknown AIDs get a RID hint only', () => {
|
||||
assert.strictEqual(jcAidName('A000000151000000'), 'GlobalPlatform Issuer Security Domain');
|
||||
assert.strictEqual(jcAidName('A0000001515350'), 'GlobalPlatform RID');
|
||||
assert.strictEqual(jcAidName('A0000000871002FFFFFFFF8907090000'), '3GPP RID');
|
||||
// no pinned name for the legacy ISD AID, and vendor AIDs stay unnamed
|
||||
assert.strictEqual(jcAidName('A000000003000000'), '');
|
||||
assert.strictEqual(jcAidName('D276000005AAFFCAFE0010'), '');
|
||||
assert.strictEqual(jcAidName('A1130001180001'), '');
|
||||
});
|
||||
|
||||
test('the resolver normalizes case and spacing and rejects malformed input', () => {
|
||||
assert.strictEqual(jcAidName('a0 00 00 00 62 01 01'), 'javacard.framework');
|
||||
assert.strictEqual(jcAidName('a0000000620101'), 'javacard.framework');
|
||||
assert.strictEqual(jcAidName(''), '');
|
||||
assert.strictEqual(jcAidName(null), '');
|
||||
assert.strictEqual(jcAidName(undefined), '');
|
||||
assert.strictEqual(jcAidName('AB'), '');
|
||||
assert.strictEqual(jcAidName('A000000062010'), ''); // odd hex length
|
||||
});
|
||||
|
||||
test('the plain-text and HTML suffixes name known AIDs only', () => {
|
||||
assert.strictEqual(jcAidSuffix('A0000000620101'), ' (javacard.framework)');
|
||||
assert.strictEqual(jcAidSuffix('A0000001515350'), ' (GlobalPlatform RID)');
|
||||
assert.strictEqual(jcAidSuffix('D276000005AAFFCAFE0010'), '');
|
||||
globalThis.esc = s => 'E(' + s + ')';
|
||||
assert.strictEqual(jcAidHtml('A0000000620101'),
|
||||
'E(A0000000620101) <span class="text-gray-400 dark:text-slate-500">(E(javacard.framework))</span>');
|
||||
assert.strictEqual(jcAidHtml('D276000005AAFFCAFE0010'), 'E(D276000005AAFFCAFE0010)');
|
||||
globalThis.esc = s => s;
|
||||
});
|
||||
|
||||
test('the package table is well-formed', () => {
|
||||
const keys = Object.keys(JC_AID_NAMES);
|
||||
assert.ok(keys.length >= 45, 'entries: ' + keys.length);
|
||||
keys.forEach(k => {
|
||||
assert.match(k, /^[0-9A-F]{10,32}$/, k);
|
||||
assert.strictEqual(k.length % 2, 0, k);
|
||||
assert.ok(typeof JC_AID_NAMES[k] === 'string' && JC_AID_NAMES[k].length > 0, k);
|
||||
});
|
||||
Object.keys(JC_AID_RIDS).forEach(k => assert.match(k, /^[0-9A-F]{10}$/, k));
|
||||
});
|
||||
@@ -23,7 +23,8 @@ function extractFunc(src, name) {
|
||||
|
||||
// Extract chain builder functions and dependencies
|
||||
const FNS = ['berLenStr', 'buildApdu', 'escHtml', 'esc', 'chainInit', 'chainRamBuildRowHex', 'ramFmtLifecycle', 'ramFmtPrivileges', 'ramRenderExploreHtml',
|
||||
'ramCardIdxAfterRemove', 'ramClearResults', 'ramHideProgress', 'ramOpChanged', 'ramRender', 'ramApplyCard', 'ramExecute'];
|
||||
'ramCardIdxAfterRemove', 'ramClearResults', 'ramHideProgress', 'ramOpChanged', 'ramRender', 'ramApplyCard', 'ramExecute',
|
||||
'jcAidName', 'jcAidSuffix', 'jcAidHtml'];
|
||||
let code = '';
|
||||
for (const f of FNS) {
|
||||
code += extractFunc(html, f) + '\n';
|
||||
@@ -32,6 +33,10 @@ const m = html.match(/const _chains = \{\};/);
|
||||
if (m) code += m[0].replace(/^const /, 'var ') + '\n';
|
||||
const lc = html.match(/const RAM_LIFECYCLE = \{[\s\S]*?\n\};/);
|
||||
if (lc) code += lc[0].replace(/^const /, 'var ') + '\n';
|
||||
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';
|
||||
eval(code);
|
||||
code += 'var _ramCardIdx = null;\nvar _ramOpLast = null;\nvar _ramExplorerData = null;\n';
|
||||
eval(code);
|
||||
@@ -192,6 +197,20 @@ test('ramRenderExploreHtml localizes every label and button', () => {
|
||||
assert.ok(seen.includes('Load File AID / Package AID:'));
|
||||
assert.ok(seen.includes('Executable Module AIDs / Applet Class AIDs:'));
|
||||
assert.ok(!out.includes('data-l10n'), out);
|
||||
// the well-known ISD AID is annotated (GP Card Spec v2.3.1 H.1.3)
|
||||
assert.ok(out.includes('A000000151000000 <span class="text-gray-400 dark:text-slate-500">(GlobalPlatform Issuer Security Domain)</span>'), out);
|
||||
});
|
||||
|
||||
test('ramRenderExploreHtml annotates standard package AIDs, vendor AIDs stay bare', () => {
|
||||
global.t = s => s;
|
||||
const out = ramRenderExploreHtml(null, [], [], [
|
||||
{ aid: 'A0000000871005FFFFFFFF8913200000', lifecycle: '01', version: '1.0',
|
||||
moduleAids: ['A000000062010101'], sdAid: 'A0000001515350' },
|
||||
]);
|
||||
delete global.t;
|
||||
assert.ok(out.includes('(uicc.usim.toolkit)'), out);
|
||||
assert.ok(out.includes('(javacard.framework.service)'), out);
|
||||
assert.ok(out.includes('(GlobalPlatform RID)'), out);
|
||||
});
|
||||
|
||||
test('ramFmtPrivileges uses the translated (none) placeholder', () => {
|
||||
|
||||
@@ -58,6 +58,10 @@ test('scp81LogLine renders script entries', () => {
|
||||
'13 script-memory applets=4 free NV=61600 free vol=2048');
|
||||
});
|
||||
|
||||
eval(html.match(/const JC_AID_NAMES = \{[\s\S]*?\n\};/)[0].replace(/^const /, 'var '));
|
||||
eval(html.match(/const JC_AID_RIDS = \{[\s\S]*?\n\};/)[0].replace(/^const /, 'var '));
|
||||
eval(extractFunc(html, 'jcAidName'));
|
||||
eval(extractFunc(html, 'jcAidSuffix'));
|
||||
eval(extractFunc(html, 'scp81DecodeGetStatus'));
|
||||
eval(extractFunc(html, 'scp81GroupResults'));
|
||||
eval(extractFunc(html, 'scp81ResultLines'));
|
||||
@@ -105,6 +109,15 @@ test('scp81ResultLines decodes GET STATUS entries', () => {
|
||||
}
|
||||
});
|
||||
|
||||
test('scp81ResultLines annotates standard package AIDs and RID hints', () => {
|
||||
const lines = scp81ResultLines({ apdu: '80F24002024F0000', results: [
|
||||
{ rapdu: 'E30E4F08A0000001510000009F70010F', sw: '9000' },
|
||||
{ rapdu: 'E3174F07A00000015153509F7001018408A000000062010101', sw: '9000' },
|
||||
] });
|
||||
assert.strictEqual(lines[0], 'A000000151000000 (GlobalPlatform Issuer Security Domain) life=0F');
|
||||
assert.strictEqual(lines[1], 'A0000001515350 (GlobalPlatform RID) life=01 module=A000000062010101 (javacard.framework.service)');
|
||||
});
|
||||
|
||||
eval(extractFunc(html, 'scp81Ascii'));
|
||||
eval(extractFunc(html, 'scp81Bcd'));
|
||||
eval(extractFunc(html, 'scp81DecodeAdminParams'));
|
||||
|
||||
Reference in New Issue
Block a user