From 0fa9548c59936ee0792a66d9113d435f81915740 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=90=D0=BD=D1=82=D0=BE=D0=BD=20=D0=A2=D1=80=D0=BE=D1=88?= =?UTF-8?q?=D0=B8=D0=BD?= Date: Sat, 12 Sep 2026 08:22:02 +0300 Subject: [PATCH] docs: cover C-APDU parser, HTTP OTA, profiler, snapshots README(.md/_RUS.md) gain the sections that existed only in the in-app help: C-APDU Parser, HTTP OTA (GP GPC v2.2 Amd B v1.1 4.7), and the Profiler subtree (profile list, filesystem rules, scan options, check report incl. Only mismatches, card snapshots, Check card snapshot and Compare snapshots). Also correct the tab/sub-tab counts and add Theme/Localization sections to the English README. docs/api.md: remove the duplicate /api/ram-install section, document fci_hex/file_size/record_len/num_of_rec in /api/select, the records shape of /api/read, bump the version example, and add the missing endpoint sections (events, event-send, proactive-log, status-poll, rescue, poll-status, poll-toggle, pli-qualifiers, pli-dict). --- README.md | 61 ++++++++++++++++++- README_RUS.md | 55 ++++++++++++++++- docs/api.md | 160 ++++++++++++++++++++++++++++++++------------------ 3 files changed, 214 insertions(+), 62 deletions(-) diff --git a/README.md b/README.md index f4fb6d5..058f6a3 100644 --- a/README.md +++ b/README.md @@ -31,13 +31,13 @@ npm run build ## Interface -Four top-level tabs: **C-APDU**, **SCP80**, **Response parser**, **Card reader**. The C-APDU and SCP80 tabs each have sub-tabs. +Four top-level tabs: **C-APDU**, **SCP80**, **Response parser**, **Card reader**. The **C-APDU** and **SCP80** tabs use pill sub-tabs, and the Card reader tab has six sub-tabs: **File manager**, **Custom files**, **Profiler**, **pySim command line**, **Raw APDU**, and **Proactive UICC**. --- ## C-APDU tab -Builds command APDUs (C-APDUs). Five sub-tabs cover different card generations and command sets. +Builds command APDUs (C-APDUs). Six sub-tabs cover different card generations and command sets. ### SIM RFM @@ -312,6 +312,26 @@ Swaps nibble pairs of an even-length hex string. - ETSI TS 102 225: Secured packet structure for (U)SIM toolkit - pySim: enc_imsi() implementation +### C-APDU Parser + +Pastes raw APDU hex and renders a collapsible tree. It auto-detects the container: an **Expanded Script** (leading `AA` or `AE80`, decoded per ETSI TS 102 226 §5.2.1) or a **Compact C-APDU chain** (a sequence of ISO 7816 C-APDUs). Each node shows its label, hex, and a short description; parent nodes expand to reveal their sub-elements. + +### HTTP OTA + +Builds the Remote Application Management over HTTP payloads defined in GlobalPlatform **GPC v2.2 Amendment B v1.1** (§4.7). Two modes: + +- **Trigger (Push SMS)** — administration session triggering parameters (`81 > 83 > 84/[85]/[86]/89`, Table 4-3). This is the message that asks the card's Security Domain to dial out and start an HTTP session. +- **Store (SD admin params)** — writes the same parameters as card (Security Domain) data via **STORE DATA in TLV mode** (`80 E2 90 00`, P1=90 = last block + BER-TLV per GP v2.2 Amendment B v1.1.3), wrapped in tag `85` (or `A5`) per Table 4-4. + +| Section | Tag | Contents | +|---|---|---| +| Connection parameters | `84` | COMPREHENSION-TLVs needed to open the TCP connection (OPEN CHANNEL per TS 102 223): Device Identities `02`, Alpha `80`, Bearer `01`, vendor TLVs. Row editor + presets, editable hex. | +| Security parameters | `85` | Table 4-6: LV PSK Identity (text), LV Key version/KID. Identifies the PSK TLS key (RFC 4279). | +| Retry policy | `86` | Table 4-7: retry counter (2 bytes, e.g. `B000`), retry waiting delay as the TS 102 223 timer TLV (`25 03 HH MM SS`), optional vendor-specific report-failure TLV. | +| HTTP POST | `89` | Tables 4-8/9/10: Host header (`8A`), X-Admin-From agent ID (`8B`), URI (`8C`) — text converted to octets. | + +The **Command Scripting template** checkbox wraps the whole `81` triggering command in the definite-length Expanded Remote Application data format (`AA`, ETSI TS 102 226 §5.2.1) for TARs that process the expanded format. **Pack into Secured packet** sends the built payload to the SCP80 tab for SPI/counter filling — insert the TAR the SD listens on (typically the OTASD TAR) there. + --- ## SCP80 tab @@ -517,6 +537,35 @@ Values persist on the server until restart. Apply → hex updates; Save → POST Type a command name in the **pySim command line** input. Usage hints appear as a tooltip after 300ms. Command autocomplete suggestions appear above the input. +### Profiler + +Verifies that a card matches a named **profile** — an ordered set of rules describing the expected file system and, optionally, file contents. Profiles are stored in `localStorage`. + +- **New profile** creates an empty ruleset; **Profile from card** scans the equipped card and generates one rule per existing file; **Import profile** loads a ruleset from JSON (the name is stored inside the file). +- Each profile row has **Check card ▶** (run against the equipped card), **Check card snapshot** (run offline against a saved snapshot), **Edit**, **Export**, and **Delete**. + +A filesystem rule is defined by: + +- **Path** — `MF`-rooted (e.g. `MF/7F10/6F3A`) or ADF AID-rooted (e.g. `A0000000871002/6F07`). +- **FCP/FCI check** — **Filetype only (FCP)**, **Filetype + size (FCP)** (adds file size, or record length/count for record files), or **Exact FCI** (byte-for-byte comparison of the raw SELECT FCP template `'62'`, catching FID/AID, life-cycle status, security-attribute, and proprietary-parameter changes). +- **File attributes** — file type, size, record length and record count, taken from the FCP template (any may be left unset). +- **Check contents** (optional) — **Exact** hex equality, or **Mask** where `?` is a per-nibble wildcard (a mask with no `?` is a prefix match, e.g. `0891` for the IMSI MCC/MNC). Record files store a per-record list. + +The check report marks each verified aspect (e.g. *filetype ✓, size ✗, contents ✓*), lists mismatches as read-only monospace expected/actual fields aligned in one column, and shows a decoded per-parameter FCI comparison for FCI mismatches. Corrupt FCI data shows whatever decoded before the faulty part plus an explicit decode-failure note; record mismatches list the *matching records*. **Only mismatches** in the results header hides all passing files and keeps failures and errors only. + +#### “Profile from card” scan options + +The scan dialog asks for a profile name and offers the FCP/FCI mode described above, an **Ignore contents of files** checklist of frequently-overwritten files (all checked by default except `EF.ARR`; the header checkbox toggles the whole list) — `EF.LOCI`, `EF.PSLOCI`, `EF.EPSLOCI`, `EF.5GS3GPPLOCI`, `EF.Keys`, `EF.KeysPS`, `EF.SMS`, `EF.Kc`, `EF.KcGPRS`, `EF.LOCIGPRS`, `EF.CBMID`, `EF.SMSS`, `EF.ACC`, `EF.EPSNSC`, `EF.START-HFN`, `EF.ARR` — and two checked-by-default mask options that capture only the first 4 bytes of `EF.IMSI` and `EF.ICCID` (uncheck for exact matching). A progress line shows *N / total files* with the current path; the options are locked while scanning. Rules are created only for files that actually exist (a FCP template is returned); custom files from the **Custom files** sub-tab are included under the same existence check. + +#### Card snapshots + +The list view has two tabs — **Profiles** and **Card snapshots**. A snapshot is an immutable capture of the card filesystem: for every existing file it stores the path, symbolic name, file type, size (or record length/count), the raw FCI from the SELECT response, and the contents whenever the file is readable (no ignore list, no masking). The ICCID is decoded from EF.ICCID and shown next to the snapshot name. + +- **New snapshot** scans the card; **Import snapshot** loads JSON. +- Each snapshot row has **Open** (all captured data read-only, raw FCI with decoded FCI and contents; only the name is editable), **Export**, and **Delete**. +- **Check card snapshot** on a profile row runs the profile rules against a snapshot picked from the list, without a card reader. Files whose contents were not captured are reported as unverifiable errors. +- **Compare snapshots** compares two snapshots offline exactly like a profile check: pick the *master* snapshot and the *snapshot to check*, optionally masking the first 4 bytes of EF.IMSI/EF.ICCID (on by default), and get the same report. Every file must match exactly (exact FCI, contents); files present only in the checked snapshot are reported as extra files. + --- ## PWA @@ -526,6 +575,14 @@ OTAMan is a Progressive Web App and can be installed for offline use. Use the ** - Service worker pre-caches all assets on first visit - App icons at 192×192 and 512×512 +## Theme + +A dark theme is included. It follows the system preference and can be toggled manually with the header button (🌙/☀️); the choice is stored in `localStorage`. + +## Localization + +The interface is in English with Russian support. The language is detected from `navigator.language`; the header toggle (EN/RU) stores the choice in `localStorage`. Switching the language also re-renders visible dynamic views (profile lists, check reports, snapshots, cards, proactive views). + ## Server (pysim-otaman-server) The bundled Python server wraps [pySim](https://osmocom.org/projects/pysim/wiki) and serves both the OTAMan PWA (from `frontend/`) and a JSON API under `/api/*`. diff --git a/README_RUS.md b/README_RUS.md index 59d2fad..05aef6f 100644 --- a/README_RUS.md +++ b/README_RUS.md @@ -31,13 +31,13 @@ npm run build ## Интерфейс -Четыре вкладки: **C-APDU**, **SCP80**, **Response parser**, **Card reader**. Вкладки C-APDU и SCP80 имеют подвкладки. +Четыре вкладки: **C-APDU**, **SCP80**, **Response parser**, **Card reader**. Вкладки C-APDU и SCP80 используют пиллы-подвкладки; во вкладке Card reader шесть подвкладок: **File manager**, **Custom files**, **Profiler**, **pySim command line**, **Raw APDU** и **Proactive UICC**. --- ## Вкладка C-APDU -Построение команд APDU (C-APDU). Пять подвкладок для разных поколений карт и наборов команд. +Построение команд APDU (C-APDU). Шесть подвкладок для разных поколений карт и наборов команд. ### SIM RFM @@ -286,6 +286,26 @@ CLA = `80` (GlobalPlatform v2.3.1). Удалённое управление со - ETSI TS 102 225 - pySim: enc_imsi() +### Разбор C-APDU + +Вставьте сырой hex APDU — отобразится раскрывающееся дерево. Контейнер определяется автоматически: **Expanded Script** (ведущие `AA` или `AE80`, разбор по ETSI TS 102 226 §5.2.1) или **компактная цепочка C-APDU** (последовательность ISO 7816 C-APDU). У каждого узла — метка, hex и краткое описание; родительские узлы раскрываются до подэлементов. + +### HTTP OTA + +Сборка payload-ов Remote Application Management over HTTP по GlobalPlatform **GPC v2.2 Amendment B v1.1** (§4.7). Два режима: + +- **Trigger (Push SMS)** — параметры запуска административной сессии (`81 > 83 > 84/[85]/[86]/89`, Table 4-3). Это сообщение просит Security Domain карты выйти в сеть и начать HTTP-сессию. +- **Store (SD admin params)** — запись тех же параметров как данных карты (Security Domain) через **STORE DATA в TLV-режиме** (`80 E2 90 00`, P1=90 = последний блок + BER-TLV по GP v2.2 Amendment B v1.1.3), обёрнутых в тег `85` (или `A5`) по Table 4-4. + +| Секция | Тег | Содержимое | +|---|---|---| +| Параметры соединения | `84` | COMPREHENSION-TLV для открытия TCP-соединения (OPEN CHANNEL по TS 102 223): Device Identities `02`, Alpha `80`, Bearer `01`, вендорские TLV. Редактор строк + пресеты, редактируемый hex. | +| Параметры безопасности | `85` | Table 4-6: LV PSK Identity (текст), LV Key version/KID. Идентифицирует ключ PSK TLS (RFC 4279). | +| Политика повторов | `86` | Table 4-7: счётчик повторов (2 байта, напр. `B000`), задержка повтора как timer TLV TS 102 223 (`25 03 HH MM SS`), опциональный вендорский TLV отчёта об ошибке. | +| HTTP POST | `89` | Tables 4-8/9/10: заголовок Host (`8A`), X-Admin-From agent ID (`8B`), URI (`8C`) — текст преобразуется в октеты. | + +Чекбокс **Command Scripting template** оборачивает всю команду `81` в формат Expanded Remote Application с определённой длиной (`AA`, ETSI TS 102 226 §5.2.1) для TAR, обрабатывающих расширенный формат. **Pack into Secured packet** отправляет собранный payload на вкладку SCP80 для заполнения SPI/счётчика — укажите там TAR, который слушает SD (обычно OTASD). + --- ## Вкладка SCP80 @@ -487,6 +507,35 @@ Delivery PoR (SPI2 `01`) проще — карта возвращает PoR на Введите имя команды в **pySim command line**. Подсказки по использованию появляются через 300 мс. Автодополнение команд — над полем ввода. +### Профайлер + +Проверка соответствия карты именованному **профилю** — упорядоченному набору правил, описывающих ожидаемую файловую систему и (опционально) содержимое файлов. Профили хранятся в `localStorage`. + +- **Новый профиль** создаёт пустой набор правил; **Профиль с карты** сканирует подключённую карту и создаёт по правилу на каждый существующий файл; **Импорт профиля** загружает набор из JSON (имя хранится внутри файла). +- В каждой строке профиля: **Проверить карту ▶** (на подключённой карте), **Проверить снимок карты** (offline по сохранённому снимку), **Редактировать**, **Экспорт** и **Удалить**. + +Правило файловой системы задаётся: + +- **Путь** — от `MF` (напр. `MF/7F10/6F3A`) или от AID ADF (напр. `A0000000871002/6F07`). +- **Проверка FCP/FCI** — **Только тип файла (FCP)**, **Тип файла + размер (FCP)** (добавляет размер файла или длину/число записей) либо **Полный FCI** (побайтовое сравнение сырого шаблона FCP `'62'` из ответа SELECT — ловит изменения FID/AID, life-cycle, security attributes и проприетарных параметров). +- **Атрибуты файла** — тип, размер, длина и число записей из шаблона FCP (любое можно не задавать). +- **Проверка содержимого** (опционально) — **Exact** (точное равенство hex) или **Mask**, где `?` — пониббловый джокер (маска без `?` — префиксное совпадение, напр. `0891` для MCC/MNC IMSI). Для record-файлов хранится список записей. + +Отчёт проверки помечает каждый аспект (напр. *тип файла ✓, размер ✗, содержимое ✓*), показывает расхождения как поля только для чтения (ожидаемое/фактическое в одной колонке) и декодированное сравнение параметров FCI для расхождений FCI. Повреждённые FCI показывают всё, что удалось декодировать, плюс явное сообщение об ошибке; для записей указываются *совпадающие записи*. Опция **«Только расхождения»** скрывает все совпавшие файлы, оставляя несовпадения и ошибки. + +#### Опции сканирования «Профиль с карты» + +Диалог сканирования запрашивает имя профиля и предлагает режим FCP/FCI, список **«Игнорировать содержимое файлов»** (все включены, кроме `EF.ARR`; чекбокс в заголовке переключает весь список) — `EF.LOCI`, `EF.PSLOCI`, `EF.EPSLOCI`, `EF.5GS3GPPLOCI`, `EF.Keys`, `EF.KeysPS`, `EF.SMS`, `EF.Kc`, `EF.KcGPRS`, `EF.LOCIGPRS`, `EF.CBMID`, `EF.SMSS`, `EF.ACC`, `EF.EPSNSC`, `EF.START-HFN`, `EF.ARR` — и две включённые по умолчанию маски, сохраняющие только первые 4 байта `EF.IMSI` и `EF.ICCID`. Строка прогресса показывает *N / всего файлов* с текущим путём; во время сканирования опции заблокированы. Правила создаются только для существующих файлов; пользовательские файлы из подвкладки **Custom files** проверяются на существование так же. + +#### Снимки карт + +Представление списка имеет две вкладки — **«Профили»** и **«Снимки карт»**. Снимок — неизменяемая фиксация файловой системы: путь, символьное имя, тип, размер (или длина/число записей), сырой FCI и содержимое (если читается) каждого существующего файла. ICCID декодируется из EF.ICCID и показывается рядом с именем. + +- **Новый снимок** сканирует карту; **Импорт снимка** загружает JSON. +- В строке снимка: **Открыть** (все данные только для чтения, сырой FCI с декодированным и содержимое; редактируется только имя), **Экспорт**, **Удалить**. +- **Проверить снимок карты** в строке профиля выполняет правила профиля на выбранном снимке без картридера. Файлы без захваченного содержимого помечаются как непроверяемые ошибки. +- **Сравнить снимки** сравнивает два снимка offline так же, как проверка профиля: выберите *эталонный* снимок и *снимок для проверки*, при необходимости включите маску первых 4 байт EF.IMSI/EF.ICCID (включена по умолчанию). Всё должно совпадать точно (FCI, содержимое); файлы только в проверяемом снимке помечаются как лишние. + --- ## PWA @@ -502,7 +551,7 @@ OTAMan — Progressive Web App. Можно установить для offline- ## Локализация -Интерфейс на английском с поддержкой русского языка. Язык определяется из `navigator.language`. Кнопка переключения (EN/RU) в заголовке сохраняет выбор в `localStorage`. +Интерфейс на английском с поддержкой русского языка. Язык определяется из `navigator.language`. Кнопка переключения (EN/RU) в заголовке сохраняет выбор в `localStorage`. При переключении языка динамические представления (списки профилей, отчёты проверок, снимки, карты, proactive) перерисовываются. ## Совместимость версий diff --git a/docs/api.md b/docs/api.md index beccea2..0a69837 100644 --- a/docs/api.md +++ b/docs/api.md @@ -55,7 +55,7 @@ Returns server version for compatibility checking. **Example response:** ```json -{"version": "1.9.12"} +{"version": "1.9.28"} ``` ### `GET /api/status` @@ -213,60 +213,6 @@ and the decoded SPI fields. "diffs": [], "spi": {"counter": "counter_must_be_higher", ...}} ``` -### `POST /api/ram-install` - -Install a Java Card `.cap` file on the card via GlobalPlatform commands (INSTALL[for load] → LOAD ×N → INSTALL[for install (+ make selectable)]) wrapped in SCP80 secured packets. Each step is sent via ENVELOPE and its PoR is checked; the sequence aborts on the first PoR error. Requires pySim with `pySim.javacard.CapFile` and `pySim.global_platform` available on the server. - -**Request body:** -```json -{ - "cap_hex": "DECAFFED...", - "sd_aid": "A000000003000000", - "install_params": "C90000", - "stk_params": "", - "nv_quota": 0, - "volatile_quota": 0, - "make_selectable": true, - "spi1": "0E", "spi2": "01", - "kic": "15", "kid": "15", - "tar": "000000", - "cntr": "0000000001", - "kicKey": "D6FCC023...", - "kidKey": "1B07E7E0..." -} -``` - -| Field | Req | Description | -|---|---|---| -| `cap_hex` | yes | Even-length hex of the `.cap` file (zipped Java Card CAP), max 48 kB (98304 hex chars) | -| `sd_aid` | no | Security Domain AID for INSTALL[for load]; empty → default ISD `A000000003000000` | -| `install_params` | no | Hex C9 TLV install parameters; if empty, `gen_install_parameters()` is used with the quota/stk params | -| `stk_params` | no | Hex CA TLV (TS 102 226 §8.2.1.3.2.1) for SIM toolkit app-specific params | -| `nv_quota` / `volatile_quota` | no | Integer memory quotas (bytes) for `gen_install_parameters()` | -| `make_selectable` | no | If true (default), final INSTALL uses P1=`0C` (install + make selectable) | - -**Response (success):** -```json -{"success": true, "failed_step": null, - "steps": [{"name": "install_for_load", "apdu": "80E60200...", "por_status": "por_ok", "sw": "9000"}, - {"name": "load_0", "apdu": "80E80000...", "por_status": "por_ok", "sw": "9000"}, - {"name": "install_for_install", "apdu": "80E60C00...", "por_status": "por_ok", "sw": "9000"}], - "final_cntr": "0000000004", - "load_file_aid": "A000000003000000", - "module_aid": "A000000003000000", - "application_aid": "A000000003000000"} -``` - -**Response (failure):** -```json -{"success": false, "failed_step": "load_1", - "steps": [{"name": "install_for_load", "por_status": "por_ok", "sw": "9000"}, - {"name": "load_1", "por_status": "rc_error", "sw": null}], - "error": "..."} -``` - -The `steps` array contains one entry per GP command. `final_cntr` is the counter value after all successful steps (use it to update the card preset). The response is not streamed — all steps run server-side before the JSON is returned. - ### `GET /api/menu` Returns the SIM Toolkit SETUP MENU captured from the card's TERMINAL PROFILE @@ -327,11 +273,17 @@ Read file content. Auto-detects transparent vs record files. {"name": "EF.ICCID", "fid": "2FE2", "parent_sel": "3F00", "mode": "raw"} ``` -Returns: +Returns transparent data: ```json {"success": true, "sw": "9000", "file_type": "transparent", "data": "..."} ``` +Returns records: +```json +{"success": true, "sw": "9000", "file_type": "linear_fixed", + "records": [{"num": 1, "data": "..."}, {"num": 2, "data": "..."}]} +``` + ### `POST /api/write` Write raw hex data to a file. @@ -360,9 +312,16 @@ Select a file by name or FID, with optional parent selection. Returns: ```json -{"name": "EF.ICCID", "fid": "2FE2", "file_type": "transparent", "exists": true} +{"name": "EF.ICCID", "fid": "2FE2", "file_type": "transparent", + "file_size": 10, "record_len": null, "num_of_rec": null, + "fci_hex": "621082024021...", "exists": true} ``` +`fci_hex` is the raw FCP template (`'62'`) from the SELECT response, used by +the PWA's Exact FCI checks; `file_size` / `record_len` / `num_of_rec` drive +the profiler's size and record checks. When the file does not exist the +endpoint responds `404` with `{"error": "...", "exists": false}`. + ### `POST /api/tree` Get directory listing with typed children. @@ -375,3 +334,90 @@ Returns: ```json {"exists": true, "name": "MF", "fid": "3F00", "file_type": "df", "children": [{"name": "EF.ICCID", "fid": "2fe2", "isDir": false}]} ``` + +### `GET /api/events` + +Returns the event list captured from the card's SET UP EVENT LIST (an array of +event byte values, or `[]` when none was received). + +### `POST /api/event-send` + +Sends an `ENVELOPE(Event Download)` for a subscribed event. + +```json +{"event_type": 4, "event_data": "01A0"} +``` + +`event_type` is required (the SET UP EVENT LIST event byte); `event_data` is +optional hex for events that carry data. Returns the SW and any response data: + +```json +{"sw": "9000", "data": "..."} +``` + +### `GET /api/proactive-log` + +Returns the last 50 proactive commands fetched during CAT sessions, newest +first: + +```json +[{"type_hex": "25", "type_name": "SET UP MENU", "elapsed": 3.2, "bytes": 97}] +``` + +### `POST /api/status-poll` + +Manually sends `STATUS` (F2) and, if the card answers `91XX`, runs the +proactive chain (FETCH → TERMINAL RESPONSE) until it settles. Returns: + +```json +{"sw": "9000", "proactive": true} +``` + +### `POST /api/rescue` + +Recovers a stuck CAT session by clearing the pending state and re-sending the +TERMINAL PROFILE. Returns whether a menu and event list were captured again: + +```json +{"menu": true, "events": [4, 5]} +``` + +### `GET /api/poll-status` + +Background STATUS polling state. + +```json +{"enabled": true, "interval": 300} +``` + +### `POST /api/poll-toggle` + +Turns background STATUS polling on or off. + +```json +{"enabled": true} +``` + +Returns the new state (`{"enabled": ..., "interval": ...}`). + +### `GET /api/pli-qualifiers` + +Lists the PROVIDE LOCAL INFORMATION qualifier codes with their names. + +```json +[{"code": "00", "name": "Location Information"}, {"code": "0A", "name": "Battery Charge Level"}] +``` + +### `GET /api/pli-dict` + +Returns the current PLI data dictionary as a qualifier-code map. + +```json +{"00": "0291...", "0A": "64"} +``` + +### `POST /api/pli-dict` + +Updates dictionary entries. Body is a map of qualifier code to hex value; keys +must be known qualifiers and values valid hex, otherwise they are ignored. +Returns the updated dictionary.