cb40662e4e
Every preset TAR entry carries `keyset` (int 1-15) - the keyset every
operation addressed to that TAR uses: mandatory for any non-zero MSL
(the counters are keyset-linked), optional for MSL 00 (allowed: some
cards reject the no-security 00/00 KIc/KID form). The store fills it
automatically when the preset has a single keyset, the validation
refuses an entry naming an undefined keyset, and the schema is 6 (a v5
file loads leniently; editing it forces the fix).
The keyset selectors are gone: the SCP80 TAR field became a select of
the preset's entries (applying the TAR's keyset to the form and seeding
the SPI1 from its MSL); the RAM operations use the preset's ISD role
entry (the PWA sends only the preset id, the ISD TAR, SPI and the
counter override - the server resolves the keyset and the keys:
presets.tar_keyset, _preset_tar_keyset, _resolve_operation_keyset,
_op_keyset, used by send-ota's APDU path, ram-install, ram-install-app
and cap-compat); the counter probe and the test runner's scp80 steps
default to their TAR's keyset (an explicit kvn stays an override). A
security TAR without a keyset refuses the operation ("set it in the
Cards tab") - never a silent keyless send - and the start buttons wait
for a usable operation-TAR keyset.
The TAR probe checks the preset's TAR list only (the standard
TAR_PROBE_TARS list and the custom-TAR localStorage are gone; a `tars`
list selects a subset, a TAR outside the preset is refused), each row
using its own MSL and default keyset. The Cards editor's TAR table
gained a Keyset column (the only keyset preselected, an amber marker
when a security MSL lacks one).
Docs: help EN/RU, api.md's tar-probe contract, both AGENTS. Tests: the
store (the field, the auto-fill, the schema, the string forms), the
server resolution (send-ota + the refusal, the probe's per-row keysets
and refusals), the PWA (the TAR select, the RAM ISD resolution, the
keyset-free requests) - several fixtures updated for the new schema.
891 frontend + 885 Python; sw cache simple-v434.
134 lines
5.7 KiB
JavaScript
134 lines
5.7 KiB
JavaScript
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 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);
|
|
}
|
|
|
|
let code = '';
|
|
for (const fn of ['esc', 'escHtml', 'tarProbeVerdictLabel', 'tarProbeHtml',
|
|
'tarProbeRowsHtml', 'tarProbeAllRows', 'tarProbeChecked', 'cardsTarList']) {
|
|
code += extractFunc(html, fn) + '\n';
|
|
}
|
|
code += 'function t(s){return s;}\n';
|
|
eval(code);
|
|
|
|
test('tarProbeRowsHtml renders the preset rows checked', () => {
|
|
// v3.26.0: the rows come from the preset - no custom rows, no remove
|
|
// buttons
|
|
const out = tarProbeRowsHtml([
|
|
{ tar: '000000', label: 'ISD', doc: 'MSL 16 \u00b7 Keyset 1' },
|
|
{ tar: 'AF4D02', label: 'My applet' },
|
|
]);
|
|
assert.match(out, /value="000000" checked/);
|
|
assert.match(out, /title="MSL 16/);
|
|
assert.match(out, /ISD/);
|
|
assert.match(out, /value="AF4D02" checked/);
|
|
assert.ok(!out.includes('tarProbeRemoveCustom'), 'no custom rows anymore');
|
|
// escaping
|
|
const evil = tarProbeRowsHtml([{ tar: 'AA<BB', label: '<i>x' }]);
|
|
assert.ok(!evil.includes('AA<BB'));
|
|
assert.ok(!evil.includes('<i>x'));
|
|
});
|
|
|
|
test('tarProbeAllRows lists the selected preset TARs (v3.26.0)', () => {
|
|
globalThis.cards = [{ name: 'C', tars: [
|
|
{ role: 'isd', tar: '000000', msl: '16', keyset: 1 },
|
|
{ tar: 'AF4D02', msl: '0A', desc: 'My applet' }] }];
|
|
globalThis.tarPresetIdx = () => 0;
|
|
assert.deepStrictEqual(tarProbeAllRows(), [
|
|
{ tar: '000000', label: 'ISD', doc: 'MSL 16 \u00b7 Keyset 1' },
|
|
{ tar: 'AF4D02', label: 'My applet', doc: 'MSL 0A' },
|
|
]);
|
|
// no preset: no rows
|
|
globalThis.tarPresetIdx = () => -1;
|
|
assert.deepStrictEqual(tarProbeAllRows(), []);
|
|
delete globalThis.tarPresetIdx;
|
|
});
|
|
|
|
test('tarProbeChecked reads the checked boxes in list order', () => {
|
|
const boxes = [
|
|
{ value: '000000', checked: true },
|
|
{ value: 'B00000', checked: false },
|
|
{ value: 'b00120', checked: true },
|
|
];
|
|
globalThis.document = { querySelectorAll: () => boxes };
|
|
assert.deepStrictEqual(tarProbeChecked(), ['000000', 'B00120']);
|
|
boxes.forEach(b => { b.checked = false; });
|
|
assert.deepStrictEqual(tarProbeChecked(), []);
|
|
});
|
|
|
|
test('tarProbeVerdictLabel names every verdict', () => {
|
|
assert.strictEqual(tarProbeVerdictLabel('registered'), 'registered');
|
|
assert.strictEqual(tarProbeVerdictLabel('no_answer'), 'accepted, no answer');
|
|
assert.strictEqual(tarProbeVerdictLabel('no_por'), 'accepted, no PoR');
|
|
assert.strictEqual(tarProbeVerdictLabel('refused'), 'refused');
|
|
assert.strictEqual(tarProbeVerdictLabel('something'), 'something');
|
|
});
|
|
|
|
test('tarProbeHtml renders the summary and one row per TAR', () => {
|
|
const out = tarProbeHtml({
|
|
success: true, registered: 2, total: 4, final_cntr: '0000000187',
|
|
results: [
|
|
{ tar: '000000', label: 'ISD', verdict: 'registered', sw: '9000', por_sw: '6D00', por_data: '' },
|
|
{ tar: 'B00000', label: 'RFM', verdict: 'registered', sw: '9000', por_sw: '6B00', por_data: '3F00' },
|
|
{ tar: 'B00001', label: 'ADF', verdict: 'no_por', sw: '9000' },
|
|
{ tar: 'B00200', label: 'RFU', verdict: 'refused', sw: '6200' },
|
|
],
|
|
});
|
|
assert.match(out, /Registered applications: <b>2 \/ 4<\/b>/);
|
|
assert.match(out, /counter 0000000187/);
|
|
assert.match(out, /text-emerald-700 dark:text-emerald-400">registered/);
|
|
assert.match(out, /text-amber-600 dark:text-amber-400">accepted, no PoR/);
|
|
assert.match(out, /text-red-600 dark:text-red-400">refused/);
|
|
assert.ok(out.includes('6D00') && out.includes('6200'));
|
|
assert.strictEqual(tarProbeHtml(null), '');
|
|
});
|
|
|
|
test('the SCP80 tab wires the TAR probe pill', () => {
|
|
assert.match(html, /data-scp80-sub="tar" onclick="scp80SwitchSubtab\('tar'\)" data-l10n="TAR probe"/);
|
|
assert.match(html, /<div id="scp80-sub-tar" class="hidden">/);
|
|
assert.ok(html.includes("document.getElementById('scp80-sub-tar').classList.toggle('hidden', name !== 'tar');"));
|
|
assert.ok(html.includes("tar: 'tar-probe'"), 'the help anchor points at the new section');
|
|
// its own preset selector and the warning banner; v3.26.0: the probe
|
|
// checks the preset's TAR list - no keyset selector, no custom TARs
|
|
assert.match(html, /id="tar-card-sel" onchange="tarApplyCard\(this.value\)"/);
|
|
assert.match(html, /id="tar-preset-warning"/);
|
|
assert.ok(!html.includes('id="tar-keyset-sel"'), 'the probe keyset selector is gone');
|
|
assert.ok(!html.includes('tarProbeAddCustom'), 'no arbitrary TARs anymore');
|
|
assert.match(html, /TARs to probe \(the preset's TAR list\)/);
|
|
// the checklist, the APDU and the Execute button
|
|
assert.match(html, /id="tar-list"/);
|
|
assert.match(html, /onclick="tarProbeSelectAll\(true\)"/);
|
|
assert.match(html, /id="tar-apdu"[^>]*value="00A40000023F00"/);
|
|
assert.match(html, /onclick="tarExecute\(\)"/);
|
|
assert.match(html, /id="tar-result"/);
|
|
// the run: the preset and the checked TARs travel - the server resolves
|
|
// each row's keyset
|
|
assert.ok(html.includes("pysimFetch('/api/tar-probe', body)"));
|
|
assert.ok(html.includes('tars: tars'));
|
|
assert.ok(!html.includes("getRamSpParams('tar-card-sel')"));
|
|
assert.ok(html.includes('if (data.final_cntr) ramSaveCntr(data.final_cntr);'));
|
|
// the RAM-era leftovers are gone
|
|
assert.ok(!html.includes('id="ram-tar-rows"'));
|
|
assert.ok(!html.includes('id="ram-tar-list"'));
|
|
assert.ok(!html.includes("op === 'tar-probe'"));
|
|
assert.ok(!html.includes('function ramTarProbe'));
|
|
});
|