Low-counter reporting, counter sync probe, Bad-format decode, expanded forms
Live findings that drove this: the "card counter 0000000000" the low-counter notice showed is not the card's counter at all (the PoR's CNTR is a copy of the command's counter, TS 102 225 5.2 table 3, and a rejection leaves it zeroed); and an earlier "no card accepts the expanded format" observation was a hand-made template whose declared length did not match its body - the card validates the lengths and answers with a Bad format TLV. - _cntr_low_fields(status) reports the verdict as a flag; card_cntr/ suggested_cntr and the UI's "(card counter ...)" text are gone - the value is not observable. The notice offers a bounded Sync counter probe instead. - POST /api/counter-probe: a doubling counter ladder (start+1, +2, +4, ...) with a read-only command until the card accepts one, then accepted+1 is persisted (monotonic, source counter-probe). Rejected attempts change nothing on the card; the search stops at a ceiling (default FFFFFFFF, far below the 40-bit maximum where the counter blocks) and after max_attempts (default 40, clamped 1-64); a non-cntr_low failure stops it at once. - _parse_response_scripting decodes the Bad format TLV (90, TS 101 220 table 7.20; error types per TS 102 226 table 5.12) and returns a dict; the PoR carries bad_format/bad_format_name and _ram_step_result fails the step with "bad format 02 (wrong length)" instead of falling through to the compact decoder (which read the AB tag as 171 commands). - _wrap_expanded_apdu(apdu, form) emits the definite (AA) or the indefinite (AE 80 ... 00 00) scripting template; the RAM command formats are compact/ expanded/expanded-ae and _ram_detect_format probes compact, then both expanded codings (the expanded probes use Le='00' and no chained GET RESPONSE, TS 102 226 5.2.1.1). The RAM form gains a Format selector. - Live-verified: the verdict carries no value; the probe walked 0x1000001/0x1000003 (low) then 0x1000007 (accepted, stored 0x1000008); the ISD executes both expanded codings (R-APDU 08A0000000030000000F80, SW 9000) and a length-mismatched template answers bad_format 02 - decoded now. - Tests: the ladder, the probe walk + persistence, params validation, both template forms, the Bad-format TLV (definite and plain tag styles), the live ISD response through _decode_por, the step-result failure, and the PWA guards; help EN/RU, docs/api.md, the repo AGENTS.md; sw cache simple-v352.
This commit is contained in:
+35
@@ -37,6 +37,7 @@ a 3.x PWA).
|
||||
| `/api/presets/delete` | POST | Delete a preset (`{id}`) |
|
||||
| `/api/presets/import` | POST | Import presets (`{presets: [...], mode: merge\|replace}`) |
|
||||
| `/api/tar-probe` | POST | Probe the card's registered OTA applications (one secured packet per TAR, TS 101 220 Annex D) |
|
||||
| `/api/counter-probe` | POST | Bounded counter sync: probe increasing counter values (start+1, +2, +4, …) with a read-only command until the card accepts one (ceiling/attempt-capped), then persist accepted+1 |
|
||||
| `/api/esim/chip` | GET | eUICC chip details (EID, EUICCInfo1/2, configured addresses) |
|
||||
| `/api/esim/profiles` | GET | Installed eSIM profiles with their metadata |
|
||||
| `/api/esim/notifications` | GET | Pending eSIM notifications (read-only) |
|
||||
@@ -246,6 +247,40 @@ import also accepts presets exported by the older localStorage-based builds.
|
||||
`"replace"`) → `{"ok": true, "added": N, "skipped": M, "errors": […]}`;
|
||||
invalid entries and duplicates are skipped and reported, never fatal.
|
||||
|
||||
### `POST /api/counter-probe`
|
||||
|
||||
Sync a preset's SCP80 counter with the card. The card accepts only a counter
|
||||
*above* its own (TS 102 225 §5.1.1, SPI1 b5b4 = `10`) and updates it only when
|
||||
the packet is accepted, so rejected attempts change nothing; the probe walks a
|
||||
doubling ladder (`start+1`, `+2`, `+4`, …) with a read-only command until one
|
||||
is accepted, then persists `accepted+1` (monotonic) into the keyset. The
|
||||
search stops at a ceiling and after a bounded number of packets, so it can
|
||||
never run towards the 40-bit maximum, where the card blocks the counter. The
|
||||
card's own counter is never reported by the card (the PoR's CNTR is a copy of
|
||||
the packet's, TS 102 225 §5.2 Table 3), which is why this probe exists.
|
||||
|
||||
```json
|
||||
{"preset_id": "…", "kvn": 2, "tar": "000000", "spi1": "16", "spi2": "21",
|
||||
"cntr": "0001000000", "ceiling": "FFFFFFFF", "max_attempts": 40,
|
||||
"apdu": "00A40000023F00"}
|
||||
```
|
||||
|
||||
All fields except `preset_id` are optional: `kvn` picks a keyset (the first
|
||||
otherwise), `tar`/`spi1`/`spi2` default to the preset's values, `cntr` to the
|
||||
keyset's counter, `ceiling` to `FFFFFFFF` (must stay below `FFFFFFFFFF`),
|
||||
`max_attempts` to 40 (clamped to 1–64), and `apdu` to a read-only `SELECT MF`
|
||||
(the probe command should never modify anything). A SPI1 without a counter
|
||||
check is refused (400).
|
||||
|
||||
**Response:** `{"success": true, "accepted_cntr": "0001000000",
|
||||
"stored_cntr": "0001000001", "packets": 3, "stopped": "accepted",
|
||||
"attempts": [{"cntr": "…", "por_status": "cntr_low"}, …],
|
||||
"start": "…", "ceiling": "FFFFFFFF", "kvn": 2}`
|
||||
|
||||
`stopped` is `accepted`, `ceiling`, `attempts` or `error`; on a non-`cntr_low`
|
||||
failure (refused ENVELOPE, wrong TAR, security error) the probe stops at the
|
||||
first attempt instead of hammering the card.
|
||||
|
||||
### `POST /api/tar-probe`
|
||||
|
||||
Probe which OTA applications the card has registered: one **secured packet per
|
||||
|
||||
Reference in New Issue
Block a user