v3.26.0: per-TAR default keysets (the preset/SCP80/RAM rework)

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.
This commit is contained in:
2026-10-14 12:21:16 +03:00
parent 6e4494195c
commit cb40662e4e
18 changed files with 955 additions and 611 deletions
+17 -15
View File
@@ -341,28 +341,30 @@ first attempt instead of hammering the card.
### `POST /api/tar-probe`
Probe which OTA applications the card has registered: one **secured packet per
TAR** (the standard allocations of TS 101 220 V18.3.0 Annex D, Table D.1 unless
a `tars` list is given), each carrying a harmless C-APDU (`SELECT MF` by
default), with the preset's keyset. Each checked TAR uses **its own MSL** when
the preset carries the entry, otherwise the request's `spi1`; a TAR that
resolves to neither is refused with a hint **before any packet is sent**. The
resolved SPI1 must carry a counter check (b5b4 ≠ 00) and ask for a PoR.
Every accepted packet consumes a counter, which is persisted into the keyset
like any other operation, so the probe is refused when the SPI1 has no counter
check or the named preset does not define the keyset number. A missing
`cntr` is seeded from that keyset (v3.20.0; an explicit value wins but never
lowers the stored counter). The delivered SMS carries the OTA CPI IE by
TAR of the named preset** (v3.26.0 — the probe checks the preset's TAR list
only; a `tars` list may select a subset, and a TAR outside the preset is
refused with `400`), each carrying a harmless C-APDU (`SELECT MF` by default).
Each row uses **its own MSL** (the request's `spi1` is only a fallback for a
TAR the preset does not carry) and **its own default keyset** — the keyset
resolved from that TAR's entry, so the request does not carry KIc/KID/keys and
the probe's keyset selector is gone. A TAR whose MSL requires security
(non-zero) without a default keyset, or one naming an undefined keyset, is
refused with a hint **before any packet is sent**. The resolved SPI1 must
carry a counter check (b5b4 ≠ 00) and ask for a PoR. Every accepted packet
consumes a counter, which is persisted into that row's keyset like any other
operation (each keyset has its own counter, TS 102 225 A.1); an explicit
`cntr` seeds the first row only. The delivered SMS carries the OTA CPI IE by
default (`includeCpi`, as in `/api/send-ota`).
```json
{"preset_id": "…", "kic": "25", "kid": "25", "kicKey": "…", "kidKey": "…",
"cntr": "0000000187", "apdu": "00A40000023F00", "tars": ["000000", "B00000"]}
{"preset_id": "…", "spi1": "16", "apdu": "00A40000023F00",
"tars": ["000000", "B00000"]}
```
**Response:** `{"success": true, "registered": N, "total": M,
"final_cntr": "…", "kvn": 2, "spi1": "16", "spi2": "01",
"results": [{"tar": "000000", "label": "Issuer Security Domain (compact)",
"spi1": "16", "verdict": "registered", "sw": "9000",
"results": [{"tar": "000000", "label": "ISD", "spi1": "16", "kvn": 2,
"verdict": "registered", "sw": "9000",
"por_status": "por_ok",
"por_sw": "6D00", "por_data": ""}], "steps": [...]}`