docs: help/README catch up with v3.8.0; keyset-number wording; RU "пресет"
Documentation:
- help (§1/§3): the SCP80 tab is switched by three pills - the tab list and the
section intro now name the TAR probe (RU "TAR-сканер"); §3.1/§3.2 describe the
Keyset selector next to the preset selector (and that a successful PoR
advances the counter only when SPI1 carries a counter check); §4 describes
the keyset editor rows, the derived number and the per-keyset counters; the
probe section keeps its counter rule.
- README/README_RUS: the SCP80 tab says three pills and gains a "TAR probe" /
"TAR-сканер" section (standard list, verdicts, counter persistence); the
Cards section is rewritten for the server-side store - keysets, server-owned
per-keyset counters with the audit trail, JSON export/import as the migration
path, the keyset editor, the equipped-card mismatch banner - and the field
table, "Add a card" and "Edit / remove" follow.
Terminology:
- "key version" is no longer used for SCP80 keysets in the UI, the server error
strings and the docs: the selector is "Keyset", its options "Keyset 1 - ...",
the messages say "keyset N ...". The TS 102 225 A.1/A.2 quotes and the GP
PUT KEY P1 field keep the spec's own wording.
- RU keeps the word "keyset" untranslated ('Добавить keyset', 'Номер keyset',
"не является keyset пресета", ...) and uses "пресет" instead of
"предустановка" throughout the dictionary, the help and README_RUS.
sw cache simple-v340. Tests: 742 Node / 619 Python.
This commit is contained in:
+16
-16
@@ -42,9 +42,9 @@
|
||||
|
||||
<h3 id="interface" class="text-lg font-medium mb-2">1.1 Интерфейс</h3>
|
||||
<ul class="list-disc list-inside text-sm space-y-1 mb-3">
|
||||
<li><strong>Шапка</strong> — версия приложения, кнопка <strong>INSTALL PWA</strong> (появляется, когда браузер предлагает установку, для офлайн-работы), ссылки на проект на GitHub и на эту справку, переключатель языка <strong>EN/RU</strong> и переключатель тёмной/светлой <strong>темы</strong>. Рядом с заголовком небольшой индикатор показывает состояние сервера/карты (серая точка = подключение, красная = нет сервера, значки карты = нет карты / инициализация / карта готова), ICCID подключённой карты, компактный значок <strong>ADM ✓</strong> (зелёный, администраторский PIN подтверждён) или <strong>ADM ✗</strong> (красный, не подтверждён) для текущей сессии карты — со значком ключа в конце (<strong>ADM ✓ ⚿</strong>), если в предустановке карты с этим ICCID есть ключ ADM — и серые маркеры <strong>SCP80</strong> / <strong>SCP81</strong>, появляющиеся, когда в этой предустановке заполнены все поля SCP80 (KIc/KID, SPI1/SPI2, счётчик и ключи) или SCP81 (PSK identity/ключ).</li>
|
||||
<li><strong>Шапка</strong> — версия приложения, кнопка <strong>INSTALL PWA</strong> (появляется, когда браузер предлагает установку, для офлайн-работы), ссылки на проект на GitHub и на эту справку, переключатель языка <strong>EN/RU</strong> и переключатель тёмной/светлой <strong>темы</strong>. Рядом с заголовком небольшой индикатор показывает состояние сервера/карты (серая точка = подключение, красная = нет сервера, значки карты = нет карты / инициализация / карта готова), ICCID подключённой карты, компактный значок <strong>ADM ✓</strong> (зелёный, администраторский PIN подтверждён) или <strong>ADM ✗</strong> (красный, не подтверждён) для текущей сессии карты — со значком ключа в конце (<strong>ADM ✓ ⚿</strong>), если в пресете карты с этим ICCID есть ключ ADM — и серые маркеры <strong>SCP80</strong> / <strong>SCP81</strong>, появляющиеся, когда в этом пресете заполнены все поля SCP80 (KIc/KID, SPI1/SPI2, счётчик и ключи) или SCP81 (PSK identity/ключ).</li>
|
||||
<li>Выбор языка и темы хранится в <code class="font-mono text-sm">localStorage</code> и сохраняется между перезагрузками.</li>
|
||||
<li>Вкладки верхнего уровня: <strong>Remote APDU</strong> (<strong>SIM RFM</strong>, <strong>USIM RFM</strong>, <strong>RAM/GP</strong>, <strong>Expanded Script</strong>, <strong>«Пуш/триггер»</strong>, <strong>«Разбор»</strong> (<strong>«Разбор C-APDU»</strong>, <strong>«Разбор R-APDU»</strong>)), <strong>SCP80</strong> (<strong>Secured Packet</strong>, <strong>RAM</strong>), <strong>SCP81</strong> (<strong>«Слушатель»</strong>, <strong>«Скрипты»</strong>), <strong>«Карты»</strong>, <strong>«Профайлер»</strong> (вкладки <strong>«Профили»</strong>, <strong>«Снимки карт»</strong>, <strong>«Пользовательские файлы»</strong>), <strong>«Картридер»</strong> (<strong>Файловый менеджер</strong>, <strong>Командная строка pySim</strong>, <strong>Отправка APDU</strong>) и <strong>«Симулятор»</strong>.</li>
|
||||
<li>Вкладки верхнего уровня: <strong>Remote APDU</strong> (<strong>SIM RFM</strong>, <strong>USIM RFM</strong>, <strong>RAM/GP</strong>, <strong>Expanded Script</strong>, <strong>«Пуш/триггер»</strong>, <strong>«Разбор»</strong> (<strong>«Разбор C-APDU»</strong>, <strong>«Разбор R-APDU»</strong>)), <strong>SCP80</strong> (<strong>Secured Packet</strong>, <strong>RAM</strong>, <strong>TAR-сканер</strong>), <strong>SCP81</strong> (<strong>«Слушатель»</strong>, <strong>«Скрипты»</strong>), <strong>«Карты»</strong>, <strong>«Профайлер»</strong> (вкладки <strong>«Профили»</strong>, <strong>«Снимки карт»</strong>, <strong>«Пользовательские файлы»</strong>), <strong>«Картридер»</strong> (<strong>Файловый менеджер</strong>, <strong>Командная строка pySim</strong>, <strong>Отправка APDU</strong>) и <strong>«Симулятор»</strong>.</li>
|
||||
<li>Ссылка <strong>справка</strong> открывает эту документацию на разделе, соответствующем текущему представлению (например, вкладка «Профайлер» открывает §7).</li>
|
||||
</ul>
|
||||
|
||||
@@ -56,7 +56,7 @@
|
||||
|
||||
<h3 id="sim-rfm" class="text-lg font-medium mb-2">2.1 SIM RFM</h3>
|
||||
<p class="mb-2">CLA = <code class="font-mono text-sm">A0</code> (GSM 11.11 / TS 151 011, ISO 7816-4). Удалённое управление файлами классических SIM-карт.</p>
|
||||
<p class="mb-2">Команды собираются в виде <strong>цепочки</strong>: нажмите кнопку <code class="font-mono text-sm">+ SELECT</code> (или другую командную кнопку), чтобы добавить строку, заполните её поля — предпросмотр цепочки (над кнопкой упаковки) обновится автоматически. Добавьте строку <strong>GET RESPONSE</strong>, чтобы получить данные после SELECT. Кнопка <strong>Упаковать в Secured packet</strong> упаковывает всю цепочку в пакет SCP80 и устанавливает TAR SCP80 в <strong>UICC RFM TAR</strong> выбранной предустановки (по умолчанию <code class="font-mono text-sm">B00000</code>) — TAR UICC Shared File System RFM: MF выбирается неявно, ADF недоступны, поэтому пути SELECT адресуют файлы от MF.</p>
|
||||
<p class="mb-2">Команды собираются в виде <strong>цепочки</strong>: нажмите кнопку <code class="font-mono text-sm">+ SELECT</code> (или другую командную кнопку), чтобы добавить строку, заполните её поля — предпросмотр цепочки (над кнопкой упаковки) обновится автоматически. Добавьте строку <strong>GET RESPONSE</strong>, чтобы получить данные после SELECT. Кнопка <strong>Упаковать в Secured packet</strong> упаковывает всю цепочку в пакет SCP80 и устанавливает TAR SCP80 в <strong>UICC RFM TAR</strong> выбранного пресета (по умолчанию <code class="font-mono text-sm">B00000</code>) — TAR UICC Shared File System RFM: MF выбирается неявно, ADF недоступны, поэтому пути SELECT адресуют файлы от MF.</p>
|
||||
<table class="w-full text-sm mb-3 border-collapse">
|
||||
<thead><tr class="border-b border-gray-300 dark:border-slate-700">
|
||||
<th class="text-left py-1 px-2">Команда</th><th class="text-left py-1 px-2">INS</th><th class="text-left py-1 px-2">Описание</th>
|
||||
@@ -109,7 +109,7 @@
|
||||
<ul class="list-disc list-inside text-sm space-y-1 mb-3">
|
||||
<li><strong>SELECT</strong> по умолчанию запрашивает FCP (P2=<code class="font-mono text-sm">04</code>) и добавляет Le=<code class="font-mono text-sm">00</code>; флажок <strong>Silent (P2=0C)</strong> выбирает файл без запроса FCP (без Le, без данных ответа).</li>
|
||||
<li><strong>По пути</strong> предлагает выбор <strong>from MF</strong> (P1=<code class="font-mono text-sm">08</code>) или <strong>from current DF</strong> (P1=<code class="font-mono text-sm">09</code>); текущий DF сессии ADF RFM — ADF.USIM.</li>
|
||||
<li><strong>Упаковать в Secured packet</strong> устанавливает TAR SCP80 в <strong>ADF RFM TAR</strong> выбранной предустановки (по умолчанию <code class="font-mono text-sm">B00001</code>), привязанный к AID ADF (ADF.USIM).</li>
|
||||
<li><strong>Упаковать в Secured packet</strong> устанавливает TAR SCP80 в <strong>ADF RFM TAR</strong> выбранного пресета (по умолчанию <code class="font-mono text-sm">B00001</code>), привязанный к AID ADF (ADF.USIM).</li>
|
||||
<li><strong>SEARCH RECORD</strong> (TS 102 221 §11.1.7) — простые режимы (P2=<code class="font-mono text-sm">10</code>/<code class="font-mono text-sm">12</code>, P1 = номер записи) и расширенный режим (P2=<code class="font-mono text-sm">18</code>) с 2-байтовым search indication; <strong>INCREASE</strong> (§11.1.8) принимает необязательный SFI и прибавляемое значение.</li>
|
||||
<li><strong>CREATE FILE</strong> / <strong>DELETE FILE</strong> / <strong>RESIZE FILE</strong> (ETSI TS 102 222 §6.3/6.4/6.10) принимают шаблон FCP; кнопка <strong>Build template</strong> собирает структурный каркас (file descriptor, FID, LCSI, размер и разрешающее компактное правило доступа <code class="font-mono text-sm">8C 02 7F 00</code> — проверьте его для своей карты). RESIZE FILE использует CLA <code class="font-mono text-sm">80</code> (TS 102 222 Table 1: <code class="font-mono text-sm">'8X'</code>/<code class="font-mono text-sm">'CX'</code>).</li>
|
||||
<li><strong>SET DATA</strong> / <strong>RETRIEVE DATA</strong> (TS 102 221 §11.3) работают с BER-TLV EF: P2 выбирает первый/следующий/повторный блок (+SFI), поле тега необязательно (<code class="font-mono text-sm">5C</code> — список размещённых тегов).</li>
|
||||
@@ -279,10 +279,10 @@
|
||||
</section>
|
||||
<section class="mb-10">
|
||||
<h2 id="scp80" class="text-xl font-semibold mb-3 border-b border-gray-300 dark:border-slate-700 pb-1">3. Вкладка SCP80</h2>
|
||||
<p class="mb-3">Верхнеуровневая вкладка <strong>SCP80</strong> объединяет разделы, связанные с SCP80. Переключение — двумя переключателями: <strong>Secured Packet</strong> и <strong>RAM</strong>. Собирает защищённые пакеты SCP80 по ETSI TS 102 225. При подключении карты сервер читает её <strong>EF.ICCID</strong> (2FE2); если в предустановке указан тот же номер (цифрами или сырым hex из EF), она автоматически выбирается в обоих представлениях. Вердикт <strong>низкого счётчика</strong> (<code class="font-mono text-sm">cntr_low</code> или предупреждающий ответ ENVELOPE, в котором всё ещё есть такой PoR) <em>не</em> исправляется повторной отправкой: любая операция SCP80/RAM немедленно останавливается, результат показывает счётчик карты и кнопку <strong>К пресету</strong>, открывающую соответствующий пресет на вкладке Cards, где счётчик нужно поднять выше — автоматически ничего не записывается.</p>
|
||||
<p class="mb-3">Верхнеуровневая вкладка <strong>SCP80</strong> объединяет разделы, связанные с SCP80. Переключение — тремя переключателями: <strong>Secured Packet</strong>, <strong>RAM</strong> и <strong>TAR-сканер</strong>. Собирает защищённые пакеты SCP80 по ETSI TS 102 225. При подключении карты сервер читает её <strong>EF.ICCID</strong> (2FE2); если в пресете указан тот же номер (цифрами или сырым hex из EF), он автоматически выбирается во всех трёх представлениях SCP80. Вердикт <strong>низкого счётчика</strong> (<code class="font-mono text-sm">cntr_low</code> или предупреждающий ответ ENVELOPE, в котором всё ещё есть такой PoR) <em>не</em> исправляется повторной отправкой: любая операция SCP80/RAM немедленно останавливается, результат показывает счётчик карты и кнопку <strong>К пресету</strong>, открывающую соответствующий пресет на вкладке Cards, где счётчик нужно поднять выше — автоматически ничего не записывается.</p>
|
||||
|
||||
<h3 id="secured-packet" class="text-lg font-medium mb-2">3.1 Secured Packet</h3>
|
||||
<p class="mb-2">Собирает защищённые пакеты SCP80 по ETSI TS 102 225.</p>
|
||||
<p class="mb-2">Собирает защищённые пакеты SCP80 по ETSI TS 102 225. Селектор <strong>Keyset</strong> рядом с выбором пресета указывает, из какого keyset берутся ключи и счётчик; отправка отклоняется, если в пресете нет такого номера keyset или KIc/KID несут разные номера (TS 102 225 A.2).</p>
|
||||
<h4 id="packet-structure" class="font-medium mb-1">Структура пакета</h4>
|
||||
<table class="w-full text-sm mb-3 border-collapse">
|
||||
<thead><tr class="border-b border-gray-300 dark:border-slate-700"><th class="text-left py-1 px-2">Поле</th><th class="text-left py-1 px-2">Размер</th><th class="text-left py-1 px-2">Описание</th></tr></thead>
|
||||
@@ -311,10 +311,10 @@
|
||||
<li>AES требует счётчик с защитой от повтора: биты SPI1 b5 b4 должны быть <code class="font-mono text-sm">10</code> (счётчик больше) или <code class="font-mono text-sm">11</code> (счётчик +1) согласно TS 102 225 §5.1.2/§5.1.3.1</li>
|
||||
<li>Байт паддинга настраивается (<code class="font-mono text-sm">00</code> по умолчанию или <code class="font-mono text-sm">FF</code>)</li>
|
||||
</ul>
|
||||
<p class="text-sm mb-3">Кнопка <strong>Проверить в pySim</strong> сверяет собранный пакет с эталонной реализацией <code class="font-mono text-sm">OtaDialectSms.encode_cmd</code>. Кнопка <strong>Отправить на карту</strong> доставляет пакет через ENVELOPE SMS-PP-DOWNLOAD (при подключении к серверу). 2-байтовый префикс CPL передаётся, когда пакет зашифрован или несёт RC/CC/DS (входит в их входные данные), а также при конкатенации SMS; одиночное незащищённое SMS сохраняет форму pySim, начинающуюся с CHL (TS 31.115 Table 1 NOTE). Под полем пакета показывается его размер и число SMS; пакет, не помещающийся в одно SMS, отправляется <strong>конкатенированной</strong> загрузкой (TS 31.115 §4.3) — пакет делится на части SMS-пользовательских данных (первое SMS 132 октета, последующие по 134; первое дополнительно несёт информационные элементы конкатенации и CPI), и сегменты отправляются по порядку. Более 5 сегментов отклоняется: это практический предел буфера конкатенации карты. Полученный Proof of Receipt декодируется и показывается строкой статуса PoR (статус, TAR, счётчик, сырой PoR); статусное слово и данные ответа последней команды подставляются в подвкладку <strong>«Разбор R-APDU»</strong> (Remote APDU → «Разбор»), а успешный PoR увеличивает счётчик повторов и очищает пакет.</p>
|
||||
<p class="text-sm mb-3">Кнопка <strong>Проверить в pySim</strong> сверяет собранный пакет с эталонной реализацией <code class="font-mono text-sm">OtaDialectSms.encode_cmd</code>. Кнопка <strong>Отправить на карту</strong> доставляет пакет через ENVELOPE SMS-PP-DOWNLOAD (при подключении к серверу). 2-байтовый префикс CPL передаётся, когда пакет зашифрован или несёт RC/CC/DS (входит в их входные данные), а также при конкатенации SMS; одиночное незащищённое SMS сохраняет форму pySim, начинающуюся с CHL (TS 31.115 Table 1 NOTE). Под полем пакета показывается его размер и число SMS; пакет, не помещающийся в одно SMS, отправляется <strong>конкатенированной</strong> загрузкой (TS 31.115 §4.3) — пакет делится на части SMS-пользовательских данных (первое SMS 132 октета, последующие по 134; первое дополнительно несёт информационные элементы конкатенации и CPI), и сегменты отправляются по порядку. Более 5 сегментов отклоняется: это практический предел буфера конкатенации карты. Полученный Proof of Receipt декодируется и показывается строкой статуса PoR (статус, TAR, счётчик, сырой PoR); статусное слово и данные ответа последней команды подставляются в подвкладку <strong>«Разбор R-APDU»</strong> (Remote APDU → «Разбор»), а успешный PoR увеличивает счётчик повторов (когда в SPI1 есть проверка счётчика — b5 b4 ≠ 00; без неё счётчик не увеличивается и не сохраняется) и очищает пакет.</p>
|
||||
|
||||
<h3 id="ram" class="text-lg font-medium mb-2">3.2 RAM</h3>
|
||||
<p class="mb-2">Выполняет операции удалённого управления приложениями (Remote Application Management) как защищённые пакеты SCP80 через SMS-PP-DOWNLOAD ENVELOPE. Карта должна поддерживать SCP03 (AES или 3DES). Предустановка карты со вкладки <strong>Карты</strong> обеспечивает SPI, ключи, TAR и счётчик (RAM использует <strong>ISD TAR</strong> предустановки, по умолчанию <code class="font-mono text-sm">000000</code>).</p>
|
||||
<p class="mb-2">Выполняет операции удалённого управления приложениями (Remote Application Management) как защищённые пакеты SCP80 через SMS-PP-DOWNLOAD ENVELOPE. Карта должна поддерживать SCP03 (AES или 3DES). Пресет карты со вкладки <strong>Карты</strong> обеспечивает SPI, ключи, TAR и счётчик (RAM использует <strong>ISD TAR</strong> пресета, по умолчанию <code class="font-mono text-sm">000000</code>). Селектор <strong>Keyset</strong> рядом с выбором пресета указывает, из какого keyset берутся ключи и счётчик операции.</p>
|
||||
|
||||
<h4 id="ram-operations" class="font-medium mb-1">Операции</h4>
|
||||
<table class="w-full text-sm mb-3 border-collapse">
|
||||
@@ -340,31 +340,31 @@
|
||||
</section>
|
||||
<section class="mb-10">
|
||||
<h3 id="tar-probe" class="text-lg font-medium mb-2">3.3 TAR-сканер</h3>
|
||||
<p class="mb-2">Третья пилюля вкладки SCP80 показывает, <strong>какие OTA-приложения зарегистрированы на карте</strong>: отправляется <strong>по одному защищённому пакету на каждый выбранный TAR</strong> — стандартные значения из TS 101 220 V18.3.0 Annex D, Table D.1, каждая строка несёт документ-источник — с безобидной C-APDU (по умолчанию <code class="font-mono text-sm">SELECT MF</code>), предустановкой и набором ключей этого представления, SPI <code class="font-mono text-sm">16/01</code> (проверка счётчика + PoR).</p>
|
||||
<p class="mb-2">Третья пилюля вкладки SCP80 показывает, <strong>какие OTA-приложения зарегистрированы на карте</strong>: отправляется <strong>по одному защищённому пакету на каждый выбранный TAR</strong> — стандартные значения из TS 101 220 V18.3.0 Annex D, Table D.1, каждая строка несёт документ-источник — с безобидной C-APDU (по умолчанию <code class="font-mono text-sm">SELECT MF</code>), пресетом и keyset этого представления, SPI <code class="font-mono text-sm">16/01</code> (проверка счётчика + PoR).</p>
|
||||
<p class="mb-2">Список предзаполнен стандартными TAR (все отмечены; кнопки <strong>Выбрать все</strong> / <strong>Снять все</strong> переключают быстро), свои TAR добавляются кнопкой <strong>Добавить TAR</strong> (три hex-байта и необязательное описание). Пользовательские записи хранятся в <code class="font-mono text-sm">localStorage</code> по возможности — их потеря безвредна.</p>
|
||||
<p class="mb-2">Каждый TAR классифицируется по ответу карты: <strong>зарегистрировано</strong> (ENVELOPE 9000 + PoR со SW и данными приложения), <strong>принято, нет ответа</strong> (PoR без R-APDU), <strong>принято, нет PoR</strong> (пакет молча отброшен) и <strong>отклонено</strong> (ENVELOPE не 9000, обычно <code class="font-mono text-sm">6200</code> для TAR без зарегистрированного приложения). Таблица результата появляется в окне операции; строка под кнопкой Execute показывает <code class="font-mono text-sm">зарегистрировано N / M</code>.</p>
|
||||
<p class="mb-2">Каждый принятый картой пакет <strong>расходует счётчик</strong>, который сохраняется в набор ключей предустановки — проба отклоняется, если в SPI1 нет проверки счётчика или в предустановке нет указанной версии ключа. На живой карте: <code class="font-mono text-sm">000000</code>, <code class="font-mono text-sm">B00000</code> и <code class="font-mono text-sm">B00001</code> зарегистрированы (приложения компактного формата), расширенный формат, USAT, мультиплексирование и CASD отклонены с <code class="font-mono text-sm">6200</code>.</p>
|
||||
<p class="mb-2">Каждый принятый картой пакет <strong>расходует счётчик</strong>, который сохраняется в keyset пресета — проба отклоняется, если в SPI1 нет проверки счётчика или в пресете нет такого номера keyset. На живой карте: <code class="font-mono text-sm">000000</code>, <code class="font-mono text-sm">B00000</code> и <code class="font-mono text-sm">B00001</code> зарегистрированы (приложения компактного формата), расширенный формат, USAT, мультиплексирование и CASD отклонены с <code class="font-mono text-sm">6200</code>.</p>
|
||||
|
||||
<h2 id="cards" class="text-xl font-semibold mb-3 border-b border-gray-300 dark:border-slate-700 pb-1">4. Вкладка «Карты»</h2>
|
||||
<p class="mb-2">Хранит предустановки карт на сервере (<code class="font-mono text-sm">~/.pysim-simple-server/card_presets.json</code>; переопределяется <code class="font-mono text-sm">--card-presets</code>), чтобы представление Secured Packet могло автоматически подставлять ключи и параметры, слушатель SCP81 HTTP OTA — находить PSK-ключи, а счётчики SCP80, израсходованные операцией, сохранялись самой операцией — закрытая вкладка или второе окно браузера больше не могут потерять инкремент. Предустановка содержит <strong>один набор ключей на версию ключа</strong> (ниббл <code class="font-mono text-sm">b8..b5</code> KIc/KID, TS 102 225 §5.1.2/A.2): KIc и KID набора должны содержать одну версию (01–0F; A.2 отвергает несовпадение), и у каждой версии ключа на карте свой счётчик (Annex A.1), поэтому счётчики ведутся по наборам. Список загружается при подключении PWA и экспортируется/импортируется как JSON (импорт принимает и предустановки старых сборок — плоские и с наборами ключей). «Карты» — верхнеуровневая вкладка. При подключении карты читается её EF.ICCID, и предустановка с тем же ICCID автоматически выбирается в обоих представлениях SCP80; если подключённая карта не совпадает <strong>ни с одной</strong> предустановкой — или выбранная предустановка принадлежит другой карте — оба представления SCP80/RAM показывают жёлтое предупреждение над выбором предустановки. Поля формы сгруппированы в два блока с рамкой: <strong>SCP80 (GSM 03.48, ETSI TS 102 225)</strong> (KIc/KID, SPI1/SPI2, TAR-ы ISD/UICC RFM/ADF RFM, счётчик и ключи) и <strong>SCP81 (HTTP OTA)</strong> (пара PSK identity/ключ с пояснением о выборе ключа). Необязательное поле <strong>ADM</strong> находится в верхней строке и сохраняется для последующего использования файловым менеджером. В шапке отображаются серые маркеры <strong>SCP80</strong>/<strong>SCP81</strong> и значок ключа на бейдже ADM, когда в предустановке с ICCID подключённой карты эти поля заполнены.</p>
|
||||
<p class="mb-2">Хранит пресеты карт на сервере (<code class="font-mono text-sm">~/.pysim-simple-server/card_presets.json</code>; переопределяется <code class="font-mono text-sm">--card-presets</code>), чтобы представление Secured Packet могло автоматически подставлять ключи и параметры, слушатель SCP81 HTTP OTA — находить PSK-ключи, а счётчики SCP80, израсходованные операцией, сохранялись самой операцией — закрытая вкладка или второе окно браузера больше не могут потерять инкремент. Пресет содержит <strong>несколько keysets</strong> — номер keyset задаётся нибблом <code class="font-mono text-sm">b8..b5</code> его KIc/KID (TS 102 225 §5.1.2/A.2): KIc и KID одного keyset должны нести один и тот же номер (01–0F; A.2 отвергает несовпадение), и у каждого keyset на карте свой счётчик (Annex A.1), поэтому счётчики ведутся по keysets. В форме keysets редактируются строками — по одной на номер, с кнопками <strong>Добавить keyset</strong> / <strong>Удалить keyset</strong>; номер в строке вычисляется из байта KIc/KID, а <code class="font-mono text-sm">00</code> («без защиты») не принимается как keyset. Список загружается при подключении PWA и экспортируется/импортируется как JSON (импорт принимает и пресеты старых сборок — плоские и с keysets). «Карты» — верхнеуровневая вкладка. При подключении карты читается её EF.ICCID, и пресет с тем же ICCID автоматически выбирается во всех трёх представлениях SCP80; если подключённая карта не совпадает <strong>ни с одним</strong> пресетом — или выбранный пресет принадлежит другой карте — все три представления SCP80 показывают жёлтое предупреждение над выбором пресета. Поля формы сгруппированы в два блока с рамкой: <strong>SCP80 (GSM 03.48, ETSI TS 102 225)</strong> (KIc/KID, SPI1/SPI2, TAR-ы ISD/UICC RFM/ADF RFM, счётчик и ключи) и <strong>SCP81 (HTTP OTA)</strong> (пара PSK identity/ключ с пояснением о выборе ключа). Необязательное поле <strong>ADM</strong> находится в верхней строке и сохраняется для последующего использования файловым менеджером. В шапке отображаются серые маркеры <strong>SCP80</strong>/<strong>SCP81</strong> и значок ключа на бейдже ADM, когда в пресете с ICCID подключённой карты эти поля заполнены.</p>
|
||||
<table class="w-full text-sm mb-3 border-collapse">
|
||||
<thead><tr class="border-b border-gray-300 dark:border-slate-700"><th class="text-left py-1 px-2">Поле</th><th class="text-left py-1 px-2">Описание</th></tr></thead>
|
||||
<tbody>
|
||||
<tr class="border-b border-gray-200 dark:border-slate-700"><td class="py-1 px-2">Name</td><td class="py-1 px-2">Понятная метка (обязательна)</td></tr>
|
||||
<tr class="border-b border-gray-200 dark:border-slate-700"><td class="py-1 px-2">ICCID</td><td class="py-1 px-2">Опциональный идентификатор карты. Кнопка <strong>С карты</strong> подставляет EF.ICCID подключённой карты (активна, только когда ICCID читается); при сохранении ICCID, уже присутствующий в списке, отклоняется (сравнение без пробелов и с учётом сырой hex-формы, конфликтующий пресет называется)</td></tr>
|
||||
<tr class="border-b border-gray-200 dark:border-slate-700"><td class="py-1 px-2">ADM</td><td class="py-1 px-2">Необязательный администраторский PIN (hex или до 8 ASCII-цифр), сохраняется в предустановке для последующего использования файловым менеджером; представлениями SCP80/SCP81 не используется</td></tr>
|
||||
<tr class="border-b border-gray-200 dark:border-slate-700"><td class="py-1 px-2">ADM</td><td class="py-1 px-2">Необязательный администраторский PIN (hex или до 8 ASCII-цифр), сохраняется в пресете для последующего использования файловым менеджером; представлениями SCP80/SCP81 не используется</td></tr>
|
||||
<tr class="border-b border-gray-200 dark:border-slate-700"><td class="py-1 px-2">KIc / KID</td><td class="py-1 px-2">Индикаторы ключа и алгоритма (например, 15 = индекс 1, 3DES-CBC2; x2 = AES); задаются вместе с ключами</td></tr>
|
||||
<tr class="border-b border-gray-200 dark:border-slate-700"><td class="py-1 px-2">SPI1 / SPI2</td><td class="py-1 px-2">Security Parameter Indicators</td></tr>
|
||||
<tr class="border-b border-gray-200 dark:border-slate-700"><td class="py-1 px-2">ISD TAR</td><td class="py-1 px-2">TAR Issuer Security Domain (TS 101 220, приложение D); используется для операций RAM/GP (представление SCP80 RAM, цепочки RAM/GP); по умолчанию по спецификации <code class="font-mono text-sm">000000</code>, подставляется при пустом значении</td></tr>
|
||||
<tr class="border-b border-gray-200 dark:border-slate-700"><td class="py-1 px-2">UICC RFM TAR</td><td class="py-1 px-2">TAR UICC Shared File System RFM (TS 102 226 §7.2, TS 101 220 приложение D): MF выбирается неявно и доступны только файлы под MF (ADF недоступны); используется представлением SIM RFM; по умолчанию <code class="font-mono text-sm">B00000</code></td></tr>
|
||||
<tr class="border-b border-gray-200 dark:border-slate-700"><td class="py-1 px-2">ADF RFM TAR</td><td class="py-1 px-2">TAR ADF RFM (TS 102 226 §7.3, TS 101 220 приложение D): ADF выбирается неявно, TAR привязан к AID приложения — ADF.USIM в представлении USIM RFM; по умолчанию <code class="font-mono text-sm">B00001</code></td></tr>
|
||||
<tr class="border-b border-gray-200 dark:border-slate-700"><td class="py-1 px-2">Counter</td><td class="py-1 px-2">Счётчик повторов (5 байт); увеличивается и сохраняется после каждой успешной отправки SCP80</td></tr>
|
||||
<tr class="border-b border-gray-200 dark:border-slate-700"><td class="py-1 px-2">Counter</td><td class="py-1 px-2">Счётчик повторов (5 байт); свой у каждого keyset, сервер увеличивает и сохраняет его после каждой операции, которая его расходует</td></tr>
|
||||
<tr class="border-b border-gray-200 dark:border-slate-700"><td class="py-1 px-2">KIc key / KID key</td><td class="py-1 px-2">16/24/32 hex-символа (ключи 8/16/24 байта 3DES) или 32/48/64 hex-символа (ключи 16/24/32 байта AES)</td></tr>
|
||||
<tr class="border-b border-gray-200 dark:border-slate-700"><td class="py-1 px-2">PSK identity</td><td class="py-1 px-2">SCP81 HTTP OTA: идентификатор, который карта присылает в TLS-рукопожатии (например, <code class="font-mono text-sm">89390100000129506903</code>)</td></tr>
|
||||
<tr><td class="py-1 px-2">PSK key</td><td class="py-1 px-2">SCP81 HTTP OTA: 32 hex-символа (16 байт) для PSK TLS-слушателя; ключ выбирается по идентификатору, который предъявляет карта</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<p class="text-sm mb-3">Столбец <strong>SCP81</strong> показывает, задана ли в предустановке рабочая пара PSK: <strong>✓</strong> (зелёный) — идентификатор и ключ заданы; <strong>⚠</strong> (жёлтый) — задано только одно из двух, и слушатель такую предустановку игнорирует; <strong>—</strong> — PSK нет. Идентификатор и ключ PSK задаются вместе. Кнопка <strong>Редактировать</strong> загружает предустановку в форму (кнопка Add становится <strong>Сохранить</strong>; <strong>Отмена</strong> очищает форму), поэтому поля можно менять без повторного ввода карты; <strong>Удалить</strong> удаляет строку. Обмен предустановками: <strong>Экспорт в файл</strong> для выгрузки и <strong>Импорт из файла</strong> для загрузки. Выбранная предустановка автоматически заполняет форму Secured Packet и устанавливает TAR для операции (RAM/GP → ISD TAR, SIM RFM → UICC RFM TAR, USIM RFM → ADF RFM TAR); TAR, введённый в форму Secured Packet вручную, сохраняется при отправке - счётчик, ключи и SPI по-прежнему берутся из предустановки; изменения сразу передаются работающему слушателю SCP81.</p>
|
||||
<p class="text-sm mb-3">Столбец <strong>SCP81</strong> показывает, задана ли в пресете рабочая пара PSK: <strong>✓</strong> (зелёный) — идентификатор и ключ заданы; <strong>⚠</strong> (жёлтый) — задано только одно из двух, и слушатель такой пресет игнорирует; <strong>—</strong> — PSK нет. Идентификатор и ключ PSK задаются вместе. Кнопка <strong>Редактировать</strong> загружает пресет в форму (кнопка Add становится <strong>Сохранить</strong>; <strong>Отмена</strong> очищает форму), поэтому поля можно менять без повторного ввода карты; <strong>Удалить</strong> удаляет строку. Обмен пресетами: <strong>Экспорт в файл</strong> для выгрузки и <strong>Импорт из файла</strong> для загрузки. Выбранный пресет автоматически заполняет форму Secured Packet и устанавливает TAR для операции (RAM/GP → ISD TAR, SIM RFM → UICC RFM TAR, USIM RFM → ADF RFM TAR); TAR, введённый в форму Secured Packet вручную, сохраняется при отправке - счётчик, ключи и SPI по-прежнему берутся из пресета; изменения сразу передаются работающему слушателю SCP81.</p>
|
||||
</section>
|
||||
|
||||
<section class="mb-10">
|
||||
@@ -373,7 +373,7 @@
|
||||
<h3 id="scp81-listener" class="text-lg font-medium mb-2">5.1 Слушатель</h3>
|
||||
<p class="mb-2">Кнопки <strong>Start</strong> / <strong>Остановить</strong> управляют целью; <strong>Перезапустить скрипт</strong> ставит выбранный скрипт в очередь заново с <code class="font-mono text-sm">force</code>, начиная прогон с первого APDU. Режимы:</p>
|
||||
<ul class="list-disc list-inside text-sm space-y-1 mb-3">
|
||||
<li><strong>PSK TLS сервер</strong> (по умолчанию) — PSK TLS-слушатель на <strong>Хост</strong>:<strong>Порт</strong>, отвечающий PSK-наборами TLS 1.2 из спецификации и ведущий HTTP-диалог административной сессии GP (заголовки <code class="font-mono text-sm">X-Admin-*</code>, <code class="font-mono text-sm">200</code> со строкой команд или <code class="font-mono text-sm">204 No Content</code>). PSK-ключи берутся из предустановок карт (вкладка <strong>«Карты»</strong>): ключ выбирается по идентификатору, который карта присылает в рукопожатии; если ни у одной предустановки нет пары, <strong>Start</strong> не запускается. Ключ не сохраняется и не записывается в журнал; незнакомый идентификатор помечается в журнале как <code class="font-mono text-sm">tls-psk-unknown</code>.</li>
|
||||
<li><strong>PSK TLS сервер</strong> (по умолчанию) — PSK TLS-слушатель на <strong>Хост</strong>:<strong>Порт</strong>, отвечающий PSK-наборами TLS 1.2 из спецификации и ведущий HTTP-диалог административной сессии GP (заголовки <code class="font-mono text-sm">X-Admin-*</code>, <code class="font-mono text-sm">200</code> со строкой команд или <code class="font-mono text-sm">204 No Content</code>). PSK-ключи берутся из пресетов карт (вкладка <strong>«Карты»</strong>): ключ выбирается по идентификатору, который карта присылает в рукопожатии; если ни у одного пресета нет пары, <strong>Start</strong> не запускается. Ключ не сохраняется и не записывается в журнал; незнакомый идентификатор помечается в журнале как <code class="font-mono text-sm">tls-psk-unknown</code>.</li>
|
||||
<li><strong>Перенаправление на внешний сервер</strong> — без локального слушателя: каждый BIP-канал подключается к указанной цели (нужны <strong>Хост</strong> и <strong>Порт</strong>), которая завершает TLS и ведёт административный диалог; запрошенный картой адрес только журналируется.</li>
|
||||
<li><strong>Проброс (адрес карты)</strong> — без слушателя и без цели: терминал подключает каждый BIP-канал к адресу, который карта запрашивает в OPEN CHANNEL (Other address + порт Transport level, только TCP-клиент, удалённое соединение; спецификации не задают порт по умолчанию, поэтому неполный запрос отклоняет канал). TLS завершается на той платформе, и используется сеть сервера (только для лаборатории).</li>
|
||||
<li><strong>Захват (dump)</strong> — принимает TCP-канал карты и записывает всё, что она отправляет (например, TLS ClientHello), не отвечая.</li>
|
||||
@@ -402,7 +402,7 @@
|
||||
<li><strong>Читать сырые данные / Декодировать</strong> — чтение выбранного файла в виде hex-дампа или таблицы декодированных полей (клиентские декодеры EF: IMSI, ICCID, SPN, списки PLMN, LOCI/PSLOCI/EPSLOCI, ADN/MSISDN, таблицы сервисов, SUME, …); серверный pySim JSON того же чтения остаётся доступен в свёрнутом блоке <em>pySim JSON (сервер)</em>. Подсвеченная кнопка — текущий вид; нажатие любой из них (пере)читывает файл. «Декодировать» недоступна, если для файла нет клиентского декодера (проверка по имени/FID до чтения).</li>
|
||||
<li><strong>Редактировать сырые данные</strong> — изменение hex-данных, <strong>Сохранить</strong> для записи (или <strong>Отмена</strong>); transparent-файлы редактируются в textarea, record-файлы — по одному полю на запись с флажками выбора. Декодированный вид доступен только для чтения — «Редактировать сырые данные» сначала переключает на hex-вид и при необходимости читает файл.</li>
|
||||
<li><strong>Проверить все файлы</strong> — обход всего дерева (включая пользовательские файлы) с пометкой каждого элемента: есть (обычный вид) или нет (красный ✗, без стрелки разворачивания); существующие пустые DF показывают <code class="font-mono text-sm">(пусто)</code>. Отображается прогресс <em>N / всего</em>, обход можно остановить; в конце — сводка «есть/нет». Файлы проверяются только при разворачивании или проверке — просмотр остаётся ленивым.</li>
|
||||
<li><strong>ADM</strong> — файлы, требующие администраторский PIN, возвращают <code class="font-mono text-sm">6982</code>/<code class="font-mono text-sm">9804</code>; если в подходящей предустановке карты (тот же ICCID) есть ключ ADM, рядом с ошибкой появляется кнопка <strong>«Проверить ADM»</strong>, а значок в заголовке (<code class="font-mono text-sm">ADM ✓/✗ ⚿</code>) становится кликабельным. Каждый неверный ключ расходует попытку: остаток показывается, а повторная попытка требует подтверждения. Заблокированный ADM здесь не восстановить — нужен ключ разблокировки карты.</li>
|
||||
<li><strong>ADM</strong> — файлы, требующие администраторский PIN, возвращают <code class="font-mono text-sm">6982</code>/<code class="font-mono text-sm">9804</code>; если в подходящем пресете карты (тот же ICCID) есть ключ ADM, рядом с ошибкой появляется кнопка <strong>«Проверить ADM»</strong>, а значок в заголовке (<code class="font-mono text-sm">ADM ✓/✗ ⚿</code>) становится кликабельным. Каждый неверный ключ расходует попытку: остаток показывается, а повторная попытка требует подтверждения. Заблокированный ADM здесь не восстановить — нужен ключ разблокировки карты.</li>
|
||||
</ul>
|
||||
|
||||
<h3 id="pysim-cmdline" class="text-lg font-medium mb-2">6.2 Командная строка pySim</h3>
|
||||
|
||||
+8
-8
@@ -44,7 +44,7 @@
|
||||
<ul class="list-disc list-inside text-sm space-y-1 mb-3">
|
||||
<li><strong>Header</strong> — the app version, an <strong>INSTALL PWA</strong> button (shown when the browser offers installation, enabling offline use), links to the project on GitHub and to this help, an <strong>EN/RU</strong> language toggle, and a dark/light <strong>theme</strong> toggle. Next to the title a small status indicator shows the server/card state (gray dot = connecting, red dot = no server connection, card icons = no card / equipping / equipped), the equipped card's <strong>ICCID</strong> digits, a compact <strong>ADM ✓</strong> (green, administrator PIN verified) or <strong>ADM ✗</strong> (red, not verified) badge for the current card session — with a trailing key glyph (<strong>ADM ✓ ⚿</strong>) when the card preset matching that ICCID carries an ADM key — and gray <strong>SCP80</strong> / <strong>SCP81</strong> markers that appear when that preset has complete SCP80 (KIc/KID, SPI1/SPI2, counter and keys) or SCP81 (PSK identity/key) settings.</li>
|
||||
<li>Language and theme choices are stored in <code class="font-mono text-sm">localStorage</code> and persist across reloads.</li>
|
||||
<li>Top-level tabs: <strong>Remote APDU</strong> (<strong>SIM RFM</strong>, <strong>USIM RFM</strong>, <strong>RAM/GP</strong>, <strong>Expanded Script</strong>, <strong>Push commands</strong>, <strong>Parser</strong> (<strong>C-APDU Parser</strong>, <strong>R-APDU parser</strong>)), <strong>SCP80</strong> (<strong>Secured Packet</strong>, <strong>RAM</strong>), <strong>SCP81</strong> (<strong>Listener</strong>, <strong>Scripts</strong>), <strong>Cards</strong>, <strong>Profiler</strong> (list tabs <strong>Profiles</strong>, <strong>Card snapshots</strong>, <strong>Custom files</strong>), <strong>Card reader</strong> (<strong>File manager</strong>, <strong>pySim command line</strong>, <strong>Raw APDU</strong>), and <strong>Simulator</strong>.</li>
|
||||
<li>Top-level tabs: <strong>Remote APDU</strong> (<strong>SIM RFM</strong>, <strong>USIM RFM</strong>, <strong>RAM/GP</strong>, <strong>Expanded Script</strong>, <strong>Push commands</strong>, <strong>Parser</strong> (<strong>C-APDU Parser</strong>, <strong>R-APDU parser</strong>)), <strong>SCP80</strong> (<strong>Secured Packet</strong>, <strong>RAM</strong>, <strong>TAR probe</strong>), <strong>SCP81</strong> (<strong>Listener</strong>, <strong>Scripts</strong>), <strong>Cards</strong>, <strong>Profiler</strong> (list tabs <strong>Profiles</strong>, <strong>Card snapshots</strong>, <strong>Custom files</strong>), <strong>Card reader</strong> (<strong>File manager</strong>, <strong>pySim command line</strong>, <strong>Raw APDU</strong>), and <strong>Simulator</strong>.</li>
|
||||
<li>The <strong>help</strong> link opens this documentation at the section matching the current view (e.g. the Profiler tab opens §7).</li>
|
||||
</ul>
|
||||
|
||||
@@ -278,10 +278,10 @@
|
||||
</section>
|
||||
<section class="mb-10">
|
||||
<h2 id="scp80" class="text-xl font-semibold mb-3 border-b border-gray-300 dark:border-slate-700 pb-1">3. SCP80 tab</h2>
|
||||
<p class="mb-3">The <strong>SCP80</strong> top-level tab groups the SCP80-related views. It is switched by two pills: <strong>Secured Packet</strong> and <strong>RAM</strong>. Assembles SCP80 secured packets per ETSI TS 102 225. When a card is equipped the server reads its <strong>EF.ICCID</strong> (2FE2); if a card preset carries the same number (digits, or the raw EF hex), it is selected automatically in both views.</p>
|
||||
<p class="mb-3">The <strong>SCP80</strong> top-level tab groups the SCP80-related views. It is switched by three pills: <strong>Secured Packet</strong>, <strong>RAM</strong> and <strong>TAR probe</strong>. Assembles SCP80 secured packets per ETSI TS 102 225. When a card is equipped the server reads its <strong>EF.ICCID</strong> (2FE2); if a card preset carries the same number (digits, or the raw EF hex), it is selected automatically in all three SCP80 views.</p>
|
||||
|
||||
<h3 id="secured-packet" class="text-lg font-medium mb-2">3.1 Secured Packet</h3>
|
||||
<p class="mb-2">Builds SCP80 secured packets per ETSI TS 102 225.</p>
|
||||
<p class="mb-2">Builds SCP80 secured packets per ETSI TS 102 225. The <strong>Keyset</strong> selector next to the card preset selector picks which keyset of the preset supplies the keys and the counter; the send is refused when the preset does not define that keyset number or when its KIc/KID carry different numbers (TS 102 225 A.2).</p>
|
||||
<h4 id="packet-structure" class="font-medium mb-1">Packet structure</h4>
|
||||
<table class="w-full text-sm mb-3 border-collapse">
|
||||
<thead><tr class="border-b border-gray-300 dark:border-slate-700"><th class="text-left py-1 px-2">Field</th><th class="text-left py-1 px-2">Size</th><th class="text-left py-1 px-2">Description</th></tr></thead>
|
||||
@@ -310,10 +310,10 @@
|
||||
<li>AES requires a replay-protected counter: SPI1 bits b5 b4 must be <code class="font-mono text-sm">10</code> (counter higher) or <code class="font-mono text-sm">11</code> (counter +1) per TS 102 225 §5.1.2/§5.1.3.1</li>
|
||||
<li>Padding byte configurable (<code class="font-mono text-sm">00</code> default, or <code class="font-mono text-sm">FF</code>)</li>
|
||||
</ul>
|
||||
<p class="text-sm mb-3">A “Verify vs pySim” button cross-checks the assembled packet against pySim’s reference <code class="font-mono text-sm">OtaDialectSms.encode_cmd</code>. A “Send to Card” button delivers it via SMS-PP-DOWNLOAD ENVELOPE (when connected to the server). The 2-byte CPL prefix is transmitted whenever the packet is ciphered or carries RC/CC/DS (it is part of their input) and whenever the packet is concatenated; a single unprotected SMS keeps pySim’s CHL-first form (TS 31.115 Table 1 NOTE). Under the packet field the app reports its size and how many SMS it takes; a packet that does not fit one SMS is sent as a <strong>concatenated</strong> download (TS 31.115 §4.3) — the packet is split into SMS user-data parts (first SM 132 octets, following ones 134; the first one additionally carries the concatenation and CPI information elements) and the segments are sent in order. More than 5 segments is refused, that being the practical limit of the card’s concatenation buffer. The returned Proof of Receipt is decoded and shown as a PoR status line (status, TAR, counter, raw PoR); the last command’s status word and response data are filled into the <strong>R-APDU parser</strong> panel, and a successful PoR advances the replay counter and clears the packet. A <strong>low-counter</strong> verdict (<code class="font-mono text-sm">cntr_low</code>, or a warning ENVELOPE answer that still carries such a PoR) is <em>not</em> recoverable by retrying: every SCP80/RAM operation stops immediately and the result states the card's counter with a <strong>Go to preset</strong> button that opens the matching preset in the Cards tab, where the counter must be raised above it — nothing is written automatically.</p>
|
||||
<p class="text-sm mb-3">A “Verify vs pySim” button cross-checks the assembled packet against pySim’s reference <code class="font-mono text-sm">OtaDialectSms.encode_cmd</code>. A “Send to Card” button delivers it via SMS-PP-DOWNLOAD ENVELOPE (when connected to the server). The 2-byte CPL prefix is transmitted whenever the packet is ciphered or carries RC/CC/DS (it is part of their input) and whenever the packet is concatenated; a single unprotected SMS keeps pySim’s CHL-first form (TS 31.115 Table 1 NOTE). Under the packet field the app reports its size and how many SMS it takes; a packet that does not fit one SMS is sent as a <strong>concatenated</strong> download (TS 31.115 §4.3) — the packet is split into SMS user-data parts (first SM 132 octets, following ones 134; the first one additionally carries the concatenation and CPI information elements) and the segments are sent in order. More than 5 segments is refused, that being the practical limit of the card’s concatenation buffer. The returned Proof of Receipt is decoded and shown as a PoR status line (status, TAR, counter, raw PoR); the last command’s status word and response data are filled into the <strong>R-APDU parser</strong> panel, and a successful PoR advances the replay counter (when SPI1 carries a counter check — b5 b4 ≠ 00; without one no counter is advanced or saved) and clears the packet. A <strong>low-counter</strong> verdict (<code class="font-mono text-sm">cntr_low</code>, or a warning ENVELOPE answer that still carries such a PoR) is <em>not</em> recoverable by retrying: every SCP80/RAM operation stops immediately and the result states the card's counter with a <strong>Go to preset</strong> button that opens the matching preset in the Cards tab, where the counter must be raised above it — nothing is written automatically.</p>
|
||||
|
||||
<h3 id="ram" class="text-lg font-medium mb-2">3.2 RAM</h3>
|
||||
<p class="mb-2">Delivers Remote Application Management operations as SCP80 secured packets via SMS-PP-DOWNLOAD ENVELOPE. The card must support SCP03 (AES or 3DES). A saved card preset from the <strong>Cards</strong> tab provides the SPI, keys, TAR, and counter (RAM uses the preset's <strong>ISD TAR</strong>, <code class="font-mono text-sm">000000</code> by default).</p>
|
||||
<p class="mb-2">Delivers Remote Application Management operations as SCP80 secured packets via SMS-PP-DOWNLOAD ENVELOPE. The card must support SCP03 (AES or 3DES). A saved card preset from the <strong>Cards</strong> tab provides the SPI, keys, TAR, and counter (RAM uses the preset's <strong>ISD TAR</strong>, <code class="font-mono text-sm">000000</code> by default). The <strong>Keyset</strong> selector next to the preset selector picks the keyset whose keys and counter the operation uses.</p>
|
||||
|
||||
<h4 id="ram-operations" class="font-medium mb-1">Operations</h4>
|
||||
<table class="w-full text-sm mb-3 border-collapse">
|
||||
@@ -342,10 +342,10 @@
|
||||
<p class="mb-2">The third SCP80 pill probes <strong>which OTA applications the card has registered</strong>: it sends <strong>one secured packet per selected TAR</strong> — the standard allocations of TS 101 220 V18.3.0 Annex D, Table D.1, each row carrying the document it comes from — with a harmless C-APDU (default <code class="font-mono text-sm">SELECT MF</code>) using the view's preset and keyset, SPI <code class="font-mono text-sm">16/01</code> (counter check + PoR).</p>
|
||||
<p class="mb-2">The list is pre-filled with the standard TARs (all checked; <strong>Select all</strong> / <strong>Select none</strong> switch them quickly) and custom TARs can be added with <strong>Add TAR</strong> (three hex bytes plus an optional description). Custom entries are kept in <code class="font-mono text-sm">localStorage</code> on a best-effort basis — losing them is harmless.</p>
|
||||
<p class="mb-2">Every TAR is classified from the card's answer: <strong>registered</strong> (ENVELOPE 9000 + a PoR carrying the application's own status word and data), <strong>accepted, no answer</strong> (a PoR without an R-APDU), <strong>accepted, no PoR</strong> (the packet was silently dropped) and <strong>refused</strong> (a non-9000 ENVELOPE, typically <code class="font-mono text-sm">6200</code> for a TAR without a registered application). The result table appears in the operation popup; the line under Execute summarises <code class="font-mono text-sm">registered N / M</code>.</p>
|
||||
<p class="mb-2">Each packet the card accepts <strong>consumes a counter</strong>, which is saved into the preset's keyset — the probe is refused when SPI1 has no counter check or the named preset does not define the key version. Observed on a live card: <code class="font-mono text-sm">000000</code>, <code class="font-mono text-sm">B00000</code> and <code class="font-mono text-sm">B00001</code> registered (compact-format applications), the expanded-format TARs, USAT, multiplexing and CASD refused with <code class="font-mono text-sm">6200</code>.</p>
|
||||
<p class="mb-2">Each packet the card accepts <strong>consumes a counter</strong>, which is saved into the preset's keyset — the probe is refused when SPI1 has no counter check or the named preset does not define the keyset number. Observed on a live card: <code class="font-mono text-sm">000000</code>, <code class="font-mono text-sm">B00000</code> and <code class="font-mono text-sm">B00001</code> registered (compact-format applications), the expanded-format TARs, USAT, multiplexing and CASD refused with <code class="font-mono text-sm">6200</code>.</p>
|
||||
|
||||
<h2 id="cards" class="text-xl font-semibold mb-3 border-b border-gray-300 dark:border-slate-700 pb-1">4. Cards tab</h2>
|
||||
<p class="mb-2">Stores card presets on the server (<code class="font-mono text-sm">~/.pysim-simple-server/card_presets.json</code>; <code class="font-mono text-sm">--card-presets</code> overrides it) so the Secured Packet view can auto-fill keys and parameters, the SCP81 HTTP OTA listener can look up PSK keys, and the SCP80 counters an operation consumed are persisted with the operation itself — a closed tab or a second browser window can no longer lose an increment. A preset carries <strong>one keyset per key version</strong> (the <code class="font-mono text-sm">b8..b5</code> nibble of KIc/KID, TS 102 225 §5.1.2/A.2): KIc and KID of a keyset must carry the same version (01–0F; A.2 rejects a mismatch), and each key version has its own counter on the card (Annex A.1), so counters are tracked per keyset. The list is loaded when the PWA connects and can be exported/imported as JSON (the import also takes presets exported by older builds, flat or keyset-shaped). Cards is a top-level tab. When the card is equipped its EF.ICCID is read and the preset with the same ICCID is selected automatically in both SCP80 views; when the equipped card matches <strong>no</strong> preset — or the selected preset belongs to another card — both SCP80/RAM views show an amber warning above the preset selector. The form groups the fields into two bordered blocks: <strong>SCP80 (GSM 03.48, ETSI TS 102 225)</strong> (KIc/KID, SPI1/SPI2, the ISD/UICC RFM/ADF RFM TARs, counter and keys) and <strong>SCP81 (HTTP OTA)</strong> (the PSK identity/key pair with the key-selection note). The optional <strong>ADM</strong> field sits in the top row and is stored for later use by the file manager. The header shows gray <strong>SCP80</strong>/<strong>SCP81</strong> markers and a key glyph on the ADM badge when the preset matching the equipped card's ICCID has those settings filled in.</p>
|
||||
<p class="mb-2">Stores card presets on the server (<code class="font-mono text-sm">~/.pysim-simple-server/card_presets.json</code>; <code class="font-mono text-sm">--card-presets</code> overrides it) so the Secured Packet view can auto-fill keys and parameters, the SCP81 HTTP OTA listener can look up PSK keys, and the SCP80 counters an operation consumed are persisted with the operation itself — a closed tab or a second browser window can no longer lose an increment. A preset carries <strong>several keysets</strong> — a keyset's number is the <code class="font-mono text-sm">b8..b5</code> nibble of its KIc/KID (TS 102 225 §5.1.2/A.2): KIc and KID of a keyset must carry the same number (01–0F; A.2 rejects a mismatch), and each keyset has its own counter on the card (Annex A.1), so counters are tracked per keyset. In the form the keysets are edited as rows — one per number, with <strong>Add keyset</strong> / <strong>Remove keyset</strong>; each row shows the number derived from its KIc/KID byte, and <code class="font-mono text-sm">00</code> (no security) is not accepted as a keyset. The list is loaded when the PWA connects and can be exported/imported as JSON (the import also takes presets exported by older builds, flat or keyset-shaped). Cards is a top-level tab. When the card is equipped its EF.ICCID is read and the preset with the same ICCID is selected automatically in all three SCP80 views; when the equipped card matches <strong>no</strong> preset — or the selected preset belongs to another card — all three SCP80 views show an amber warning above the preset selector. The form groups the fields into two bordered blocks: <strong>SCP80 (GSM 03.48, ETSI TS 102 225)</strong> (KIc/KID, SPI1/SPI2, the ISD/UICC RFM/ADF RFM TARs, counter and keys) and <strong>SCP81 (HTTP OTA)</strong> (the PSK identity/key pair with the key-selection note). The optional <strong>ADM</strong> field sits in the top row and is stored for later use by the file manager. The header shows gray <strong>SCP80</strong>/<strong>SCP81</strong> markers and a key glyph on the ADM badge when the preset matching the equipped card's ICCID has those settings filled in.</p>
|
||||
<table class="w-full text-sm mb-3 border-collapse">
|
||||
<thead><tr class="border-b border-gray-300 dark:border-slate-700"><th class="text-left py-1 px-2">Field</th><th class="text-left py-1 px-2">Description</th></tr></thead>
|
||||
<tbody>
|
||||
@@ -357,7 +357,7 @@
|
||||
<tr class="border-b border-gray-200 dark:border-slate-700"><td class="py-1 px-2">ISD TAR</td><td class="py-1 px-2">Issuer Security Domain TAR (TS 101 220 Annex D); used for RAM/GP operations (SCP80 RAM view, RAM/GP chains); spec default <code class="font-mono text-sm">000000</code>, prefilled when empty</td></tr>
|
||||
<tr class="border-b border-gray-200 dark:border-slate-700"><td class="py-1 px-2">UICC RFM TAR</td><td class="py-1 px-2">UICC Shared File System RFM TAR (TS 102 226 §7.2, TS 101 220 Annex D): the MF is implicitly selected and only files under the MF are accessible (no ADFs); used by the SIM RFM view; spec default <code class="font-mono text-sm">B00000</code></td></tr>
|
||||
<tr class="border-b border-gray-200 dark:border-slate-700"><td class="py-1 px-2">ADF RFM TAR</td><td class="py-1 px-2">ADF RFM TAR (TS 102 226 §7.3, TS 101 220 Annex D): the ADF is implicitly selected and the TAR is linked to the application's AID — ADF.USIM in the USIM RFM view; spec default <code class="font-mono text-sm">B00001</code></td></tr>
|
||||
<tr class="border-b border-gray-200 dark:border-slate-700"><td class="py-1 px-2">Counter</td><td class="py-1 px-2">Replay counter (5 bytes); advanced and saved after each successful SCP80 send</td></tr>
|
||||
<tr class="border-b border-gray-200 dark:border-slate-700"><td class="py-1 px-2">Counter</td><td class="py-1 px-2">Replay counter (5 bytes); one per keyset, advanced and saved by the server after each operation that consumes it</td></tr>
|
||||
<tr class="border-b border-gray-200 dark:border-slate-700"><td class="py-1 px-2">KIc key / KID key</td><td class="py-1 px-2">16/24/32 hex chars (8/16/24-byte 3DES) or 32/48/64 hex chars (16/24/32-byte AES) keys</td></tr>
|
||||
<tr class="border-b border-gray-200 dark:border-slate-700"><td class="py-1 px-2">PSK identity</td><td class="py-1 px-2">SCP81 HTTP OTA: the identity the card sends in the TLS handshake (e.g. <code class="font-mono text-sm">89390100000129506903</code>)</td></tr>
|
||||
<tr><td class="py-1 px-2">PSK key</td><td class="py-1 px-2">SCP81 HTTP OTA: 32 hex chars (16 bytes) for the PSK TLS listener, picked by the identity the card presents</td></tr>
|
||||
|
||||
+61
-62
@@ -528,7 +528,7 @@
|
||||
<select id="sp-card-sel" onchange="cardsApply(this.value)" class="w-full border border-gray-300 dark:border-slate-600 text-sm rounded px-3 py-1.5 dark:bg-slate-800">
|
||||
<option value="" data-l10n="— Select card —">— Select card —</option>
|
||||
</select>
|
||||
<label class="block mt-2 mb-1 text-sm font-medium text-gray-700 dark:text-slate-300" data-l10n="Key version (keyset)">Key version (keyset)</label>
|
||||
<label class="block mt-2 mb-1 text-sm font-medium text-gray-700 dark:text-slate-300" data-l10n="Keyset">Keyset</label>
|
||||
<select id="sp-keyset-sel" onchange="spKeysetChanged()" class="w-full border border-gray-300 dark:border-slate-600 text-sm rounded px-3 py-1.5 dark:bg-slate-800"></select>
|
||||
</div>
|
||||
<div id="sp-preset-warning" class="hidden mb-3 px-3 py-2 rounded border border-amber-400 dark:border-amber-600 bg-amber-50 dark:bg-amber-900/20 text-sm text-amber-700 dark:text-amber-300"></div>
|
||||
@@ -695,7 +695,7 @@
|
||||
</select>
|
||||
</div>
|
||||
<div class="flex-1">
|
||||
<label class="block mb-1 text-sm font-medium text-gray-700 dark:text-slate-300" data-l10n="Key version (keyset)">Key version (keyset)</label>
|
||||
<label class="block mb-1 text-sm font-medium text-gray-700 dark:text-slate-300" data-l10n="Keyset">Keyset</label>
|
||||
<select id="ram-keyset-sel" onchange="ramKeysetChanged()" class="w-full border border-gray-300 dark:border-slate-600 text-sm rounded px-3 py-1.5 dark:bg-slate-800"></select>
|
||||
</div>
|
||||
<div class="flex-1">
|
||||
@@ -922,7 +922,7 @@
|
||||
</select>
|
||||
</div>
|
||||
<div class="flex-1">
|
||||
<label class="block mb-1 text-sm font-medium text-gray-700 dark:text-slate-300" data-l10n="Key version (keyset)">Key version (keyset)</label>
|
||||
<label class="block mb-1 text-sm font-medium text-gray-700 dark:text-slate-300" data-l10n="Keyset">Keyset</label>
|
||||
<select id="tar-keyset-sel" onchange="tarKeysetChanged()" class="w-full border border-gray-300 dark:border-slate-600 text-sm rounded px-3 py-1.5 dark:bg-slate-800"></select>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1580,7 +1580,7 @@
|
||||
|
||||
|
||||
<div id="tab-cards" class="tab-content hidden">
|
||||
<p class="text-xs text-gray-500 dark:text-slate-400 mb-3" data-l10n="Card presets are stored on the server. JSON export/import buttons are below the card list; a preset carries one keyset per key version, each with its own counter.">Card presets are stored on the server. JSON export/import buttons are below the card list; a preset carries one keyset per key version, each with its own counter.</p>
|
||||
<p class="text-xs text-gray-500 dark:text-slate-400 mb-3" data-l10n="Card presets are stored on the server. JSON export/import buttons are below the card list; a preset carries several keysets, each with its own number and counter.">Card presets are stored on the server. JSON export/import buttons are below the card list; a preset carries several keysets, each with its own number and counter.</p>
|
||||
<div class="flex gap-2 mb-3">
|
||||
<input id="cards-name" class="flex-1 border border-gray-300 dark:border-slate-600 text-sm rounded px-3 py-1.5 dark:bg-slate-800" placeholder="Name (e.g. Foobar SIM)">
|
||||
<input id="cards-iccid" class="flex-1 border border-gray-300 dark:border-slate-600 text-sm rounded px-3 py-1.5 dark:bg-slate-800 font-mono" placeholder="ICCID (optional)">
|
||||
@@ -1610,11 +1610,11 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="mb-1 flex items-center justify-between">
|
||||
<span class="text-xs text-gray-600 dark:text-slate-400" data-l10n="OTA keysets — one per key version, each with its own counter">OTA keysets — one per key version, each with its own counter</span>
|
||||
<span class="text-xs text-gray-600 dark:text-slate-400" data-l10n="OTA keysets — each with its own number and counter">OTA keysets — each with its own number and counter</span>
|
||||
<button type="button" onclick="cardsKeysetAdd()" class="px-2 py-0.5 text-xs rounded border border-gray-300 dark:border-slate-600 hover:bg-gray-200 dark:hover:bg-slate-700" data-l10n="Add keyset">Add keyset</button>
|
||||
</div>
|
||||
<div id="cards-keysets"></div>
|
||||
<p class="text-xs text-gray-500 dark:text-slate-400" data-l10n="KIc and KID must carry the same key version (their high nibble) in the range 01-0F — TS 102 225 A.2; each key version has its own counter on the card (Annex A.1).">KIc and KID must carry the same key version (their high nibble) in the range 01-0F — TS 102 225 A.2; each key version has its own counter on the card (Annex A.1).</p>
|
||||
<p class="text-xs text-gray-500 dark:text-slate-400" data-l10n="KIc and KID must carry the same keyset number (their high nibble) in the range 01-0F — TS 102 225 A.2; each keyset has its own counter on the card (Annex A.1).">KIc and KID must carry the same keyset number (their high nibble) in the range 01-0F — TS 102 225 A.2; each keyset has its own counter on the card (Annex A.1).</p>
|
||||
</fieldset>
|
||||
<fieldset class="border border-gray-200 dark:border-slate-700 rounded p-3 mb-3">
|
||||
<legend class="px-1 text-xs font-medium text-gray-500 dark:text-slate-400" data-l10n="SCP81 (HTTP OTA)">SCP81 (HTTP OTA)</legend>
|
||||
@@ -1629,7 +1629,7 @@
|
||||
<thead><tr>
|
||||
<th class="text-left py-1 px-2 border-b border-gray-200 dark:border-slate-700" data-l10n="Name">Name</th>
|
||||
<th class="text-left py-1 px-2 border-b border-gray-200 dark:border-slate-700" data-l10n="ICCID">ICCID</th>
|
||||
<th class="text-left py-1 px-2 border-b border-gray-200 dark:border-slate-700" data-l10n="Keysets (key version: KIc/KID — counter)">Keysets (key version: KIc/KID \u2014 counter)</th>
|
||||
<th class="text-left py-1 px-2 border-b border-gray-200 dark:border-slate-700" data-l10n="Keysets (number: KIc/KID — counter)">Keysets (number: KIc/KID \u2014 counter)</th>
|
||||
<th class="text-left py-1 px-2 border-b border-gray-200 dark:border-slate-700">SPI1</th>
|
||||
<th class="text-left py-1 px-2 border-b border-gray-200 dark:border-slate-700">SPI2</th>
|
||||
<th class="text-left py-1 px-2 border-b border-gray-200 dark:border-slate-700">TAR (ISD/UICC RFM/ADF RFM)</th>
|
||||
@@ -4941,7 +4941,7 @@ function crc32Bytes(data) {
|
||||
// CPL = octets from CHL to end incl. padding; CHL = 13 + len_sig (CC 8, RC 4).
|
||||
// CPI (0x70) and CHI (null) are NOT part of the SMS packet data.
|
||||
function genSp() {
|
||||
// The packet's key version must be defined in the selected preset
|
||||
// The packet's keyset number must be defined in the selected preset
|
||||
// (TS 102 225 A.2 / the "keyset must exist" rule).
|
||||
const ksErr = spKeysetGuard();
|
||||
if (ksErr) { alert(ksErr); return; }
|
||||
@@ -7140,7 +7140,7 @@ function testStepRender() {
|
||||
{v: 'apdu', l: t('C-APDU (built with the preset)')}, {v: 'sp', l: t('Pre-built secured packet')}], 'testStepKindChanged()'));
|
||||
if (src === 'sp') html += testFormRow('Secured packet (hex)', testFormInput('test-f-sp', p.sp, '01 00 ...'));
|
||||
else html += testFormRow('C-APDU (hex)', testFormInput('test-f-apdu', p.apdu, '80E2900000'));
|
||||
html += testFormRow('Key version (kvn, optional)',
|
||||
html += testFormRow('Keyset number (optional)',
|
||||
testFormInput('test-f-kvn', p.kvn === undefined ? '' : p.kvn, t('empty = the preset\'s first keyset')));
|
||||
html += testFormRow('TAR override (hex, optional)', testFormInput('test-f-tar', p.tar, 'B00000'));
|
||||
html += testFormRow('SPI1 override (hex, optional)', testFormInput('test-f-spi1', p.spi1, '16'));
|
||||
@@ -7541,7 +7541,7 @@ async function testPollRun() {
|
||||
function testWriteBackCounter() {
|
||||
const st = _testRunState;
|
||||
if (!st || st.running) return;
|
||||
// The server persists every counter the run consumed (one per key version);
|
||||
// The server persists every counter the run consumed (one per keyset number);
|
||||
// this only keeps the local copies in step with what it reports.
|
||||
const counters = (st.scp80_counters && typeof st.scp80_counters === 'object')
|
||||
? st.scp80_counters : null;
|
||||
@@ -10823,7 +10823,7 @@ function ramGoToPreset(idx) {
|
||||
switchTab('cards');
|
||||
const i = Number(idx);
|
||||
if (Number.isInteger(i) && i >= 0 && cards[i]) cardsEdit(i);
|
||||
// Focus the counter of the key version the failed operation used (the
|
||||
// Focus the counter of the keyset number the failed operation used (the
|
||||
// keyset rows are keyed by their version; the first row otherwise).
|
||||
const kvn = spKeysetKvnOf((document.getElementById('sp-kic-hex') || {}).value) ||
|
||||
spKeysetKvnOf((document.getElementById('sp-kid-hex') || {}).value) || 0;
|
||||
@@ -11304,15 +11304,15 @@ function cardsTarValue(key, value) {
|
||||
return value || defaults[key] || '';
|
||||
}
|
||||
|
||||
// ===== OTA keysets (one per GlobalPlatform key version) =====
|
||||
// A preset carries one keyset per key version: the b8..b5 nibble of KIc/KID
|
||||
// ===== OTA keysets (one per keyset number) =====
|
||||
// A preset carries several keysets: a keyset's number is the b8..b5 nibble of KIc/KID
|
||||
// (TS 102 225 5.1.2/A.2). The version is implicit in the bytes - the "key
|
||||
// index" - and each key version has its own counter on the card ("a dedicated
|
||||
// index" - and each keyset has its own counter on the card ("a dedicated
|
||||
// counter shall be associated to each key version", Annex A.1). A packet may
|
||||
// use any key version the preset defines; '00'/'00' means no security (A.2)
|
||||
// use any keyset the preset defines; '00'/'00' means no security (A.2)
|
||||
// and needs no keyset.
|
||||
|
||||
// The key version of a KIc/KID byte, or null when it is not a hex byte.
|
||||
// The keyset number of a KIc/KID byte, or null when it is not a hex byte.
|
||||
function spKeysetKvnOf(hex) {
|
||||
const s = String(hex || '').trim().toUpperCase();
|
||||
if (!/^[0-9A-F]{2}$/.test(s)) return null;
|
||||
@@ -11340,14 +11340,14 @@ function spKeysetList(preset) {
|
||||
return [];
|
||||
}
|
||||
|
||||
// The keyset of a preset for a key version, or null.
|
||||
// The keyset of a preset with the given number, or null.
|
||||
function spKeysetFor(preset, kvn) {
|
||||
return spKeysetList(preset).find(ks => spKeysetKvnOf(ks.kic) === kvn) || null;
|
||||
}
|
||||
|
||||
// The packet's key-version rules (pure): '' when the pair is usable, else the
|
||||
// reason. With a preset, the (non-zero) key version must be defined in it -
|
||||
// a packet may use any key version, but only one the preset defines.
|
||||
// reason. With a preset, the (non-zero) keyset number must be defined in it -
|
||||
// a packet may use any keyset, but only one the preset defines.
|
||||
function spKeysetCheck(preset, kicHex, kidHex) {
|
||||
const kic = String(kicHex || '').trim().toUpperCase();
|
||||
const kid = String(kidHex || '').trim().toUpperCase();
|
||||
@@ -11358,12 +11358,12 @@ function spKeysetCheck(preset, kicHex, kidHex) {
|
||||
}
|
||||
const kicV = spKeysetKvnOf(kic), kidV = spKeysetKvnOf(kid);
|
||||
if (kicV && kidV && kicV !== kidV) {
|
||||
return t('KIc and KID must carry the same key version (TS 102 225 A.2)') + ': ' + kic + ' / ' + kid;
|
||||
return t('KIc and KID must carry the same keyset number (TS 102 225 A.2)') + ': ' + kic + ' / ' + kid;
|
||||
}
|
||||
const kvn = kicV || kidV || 0;
|
||||
if (!kvn) return ''; // '00'/'00': no security
|
||||
if (preset && !spKeysetFor(preset, kvn)) {
|
||||
return t('Key version') + ' ' + kvn + ' ' + t('is not defined in the preset') +
|
||||
return t('Keyset') + ' ' + kvn + ' ' + t('is not defined in the preset') +
|
||||
(preset.name ? ' \u2014 ' + preset.name : '');
|
||||
}
|
||||
return '';
|
||||
@@ -11380,11 +11380,11 @@ function spKeysetOptionsHtml(preset, kicHex, kidHex) {
|
||||
if (v === null) return;
|
||||
if (v === kvn) found = true;
|
||||
html += '<option value="' + v + '"' + (v === kvn ? ' selected' : '') + '>' +
|
||||
v + ' \u2014 ' + esc(ks.kic || '??') + '/' + esc(ks.kid || '??') +
|
||||
'Keyset ' + v + ' \u2014 ' + esc(ks.kic || '??') + '/' + esc(ks.kid || '??') +
|
||||
' \u2014 ' + esc(t('counter')) + ' ' + esc(ks.cntr || '') + '</option>';
|
||||
});
|
||||
if (kvn && !found) {
|
||||
html += '<option value="' + kvn + '" selected>' + kvn + ' \u2014 ' +
|
||||
html += '<option value="' + kvn + '" selected>Keyset ' + kvn + ' \u2014 ' +
|
||||
esc(t('not defined in the preset')) + '</option>';
|
||||
}
|
||||
if (!html) html = '<option value="">' + esc(t('\u2014 no keyset \u2014')) + '</option>';
|
||||
@@ -11398,7 +11398,7 @@ function cardsKeysetRowHtml(ks, i) {
|
||||
const cls = 'border border-gray-300 dark:border-slate-600 text-sm rounded px-2 py-1.5 dark:bg-slate-800 font-mono';
|
||||
const kvn = spKeysetKvnOf(ks.kic);
|
||||
return '<div class="flex gap-2 items-center mb-1">' +
|
||||
'<span id="cards-ks-' + i + '-kvn" class="w-10 text-xs font-mono text-center text-gray-500 dark:text-slate-400" title="' + esc(t('Key version (b8..b5 of KIc/KID)')) + '">' + (kvn === null ? '?' : kvn) + '</span>' +
|
||||
'<span id="cards-ks-' + i + '-kvn" class="w-10 text-xs font-mono text-center text-gray-500 dark:text-slate-400" title="' + esc(t('Keyset number (b8..b5 of KIc/KID)')) + '">' + (kvn === null ? '?' : kvn) + '</span>' +
|
||||
'<input id="cards-ks-' + i + '-kic" class="w-12 ' + cls + ' text-center" maxlength="2" placeholder="KIc" value="' + esc(ks.kic || '') + '" oninput="cardsKeysetKvnUpdate(' + i + ')">' +
|
||||
'<input id="cards-ks-' + i + '-kid" class="w-12 ' + cls + ' text-center" maxlength="2" placeholder="KID" value="' + esc(ks.kid || '') + '" oninput="cardsKeysetKvnUpdate(' + i + ')">' +
|
||||
'<input id="cards-ks-' + i + '-kic-key" class="flex-1 ' + cls + '" placeholder="KIc key (hex)" value="' + esc(ks.kicKey || '') + '">' +
|
||||
@@ -11438,8 +11438,8 @@ function cardsKeysetKvnUpdate(i) {
|
||||
const bad = (a !== null && b !== null && a !== b) || a === 0 || b === 0;
|
||||
el.textContent = a === null ? '?' : a;
|
||||
el.classList.toggle('text-red-500', !!bad);
|
||||
el.title = bad ? t('KIc and KID must carry the same key version (TS 102 225 A.2)')
|
||||
: t('Key version (b8..b5 of KIc/KID)');
|
||||
el.title = bad ? t('KIc and KID must carry the same keyset number (TS 102 225 A.2)')
|
||||
: t('Keyset number (b8..b5 of KIc/KID)');
|
||||
}
|
||||
|
||||
// The keyset rows as preset entries (empty rows are dropped).
|
||||
@@ -11461,7 +11461,7 @@ function cardsKeysetsFromForm() {
|
||||
|
||||
// ===== Keyset selectors (SCP80 / RAM) =====
|
||||
// The selector follows the KIc/KID bytes: a keyset of the selected preset that
|
||||
// matches their key version is selected; a version the preset does not define
|
||||
// matches their keyset number is selected; a number the preset does not define
|
||||
// shows as "not defined" (and the packet actions refuse it).
|
||||
function spKeysetSync() {
|
||||
const spIdx = spPresetIdx();
|
||||
@@ -11574,7 +11574,7 @@ function ramPresetIdx() {
|
||||
}
|
||||
|
||||
// '' when the SCP80 form may build a packet, else the reason. With a preset
|
||||
// selected the key version must be one of its keysets; without one only the
|
||||
// selected the keyset number must be one of its keysets; without one only the
|
||||
// byte rules apply (hand-entered keys stay possible, a workbench flow).
|
||||
function spKeysetGuard() {
|
||||
const idx = spPresetIdx();
|
||||
@@ -11649,13 +11649,13 @@ async function cardsAdd() {
|
||||
const ksErr = spKeysetCheck(null, ks.kic, ks.kid);
|
||||
if (ksErr) { alert(ksErr); return; }
|
||||
if (!spKeysetKvnOf(ks.kic) || !spKeysetKvnOf(ks.kid)) {
|
||||
alert(t('Key version 00 means "no security" and is not a preset keyset'));
|
||||
alert(t('Keyset 00 means "no security" and is not a preset keyset'));
|
||||
return;
|
||||
}
|
||||
}
|
||||
const kvns = v.keysets.map(ks => spKeysetKvnOf(ks.kic));
|
||||
if (new Set(kvns).size !== kvns.length) {
|
||||
alert(t('Duplicate key version in the keysets'));
|
||||
alert(t('Duplicate keyset number in the keysets'));
|
||||
return;
|
||||
}
|
||||
if ((v.pskKey && !v.pskIdentity) || (!v.pskKey && v.pskIdentity)) {
|
||||
@@ -11887,7 +11887,7 @@ function cardsApplyFields(idx, keepTar) {
|
||||
const smSel = document.getElementById('sp-spi2-sm');
|
||||
if (smSel) smSel.value = String((spi2Val >> 5) & 1);
|
||||
document.getElementById('sp-spi2-hex').value = c.spi2;
|
||||
// The keyset: keep the key version the form carries when this preset
|
||||
// The keyset: keep the keyset number the form carries when this preset
|
||||
// defines it, otherwise start from the preset's first keyset.
|
||||
const kicNow = (document.getElementById('sp-kic-hex') || {}).value;
|
||||
const kidNow = (document.getElementById('sp-kid-hex') || {}).value;
|
||||
@@ -18937,7 +18937,7 @@ const LANG_RU = {
|
||||
'GET DATA tag': 'Tag GET DATA',
|
||||
'Response data (hex)': 'Данные ответа (hex)',
|
||||
'— Select command —': '— Выберите команду —',
|
||||
'— select preset —': '— выберите предустановленный —',
|
||||
'— select preset —': '— выберите пресет —',
|
||||
'Error: specify SELECT parameters': 'Ошибка: укажите параметры SELECT',
|
||||
'Error: unknown command': 'Ошибка: неизвестная команда',
|
||||
'Error: specify data': 'Ошибка: укажите данные',
|
||||
@@ -19771,28 +19771,27 @@ const LANG_RU = {
|
||||
'SCP81 HTTP OTA: the listener picks the key by the PSK identity the card sends in the TLS handshake. Cards without a PSK are ignored by the listener.': 'SCP81 HTTP OTA: слушатель выбирает ключ по PSK identity, которую карта присылает в TLS-рукопожатии. Карты без PSK слушателем игнорируются.',
|
||||
'Card with this ICCID already exists': 'Карта с таким ICCID уже есть в списке',
|
||||
'Card equipped but its ICCID is not readable': 'Карта подключена, но её ICCID не читается',
|
||||
'OTA keysets — one per key version, each with its own counter': 'Наборы ключей OTA — по одному на версию ключа, у каждой свой счётчик',
|
||||
'Add keyset': 'Добавить набор ключей',
|
||||
'Remove keyset': 'Удалить набор ключей',
|
||||
'Key version (keyset)': 'Версия ключа (набор ключей)',
|
||||
'Key version': 'Версия ключа',
|
||||
'Key version (b8..b5 of KIc/KID)': 'Версия ключа (биты b8..b5 KIc/KID)',
|
||||
'Keysets (key version: KIc/KID — counter)': 'Наборы ключей (версия: KIc/KID — счётчик)',
|
||||
'OTA keysets — each with its own number and counter': 'OTA keysets — у каждого свой номер и счётчик',
|
||||
'Add keyset': 'Добавить keyset',
|
||||
'Remove keyset': 'Удалить keyset',
|
||||
'Keyset': 'Keyset',
|
||||
'Keyset number (b8..b5 of KIc/KID)': 'Номер keyset (b8..b5 KIc/KID)',
|
||||
'Keysets (number: KIc/KID — counter)': 'Keysets (номер: KIc/KID — счётчик)',
|
||||
'must be one hex byte': 'должен быть одним hex-байтом',
|
||||
'KIc and KID must carry the same key version (TS 102 225 A.2)': 'KIc и KID должны содержать одну и ту же версию ключа (TS 102 225 A.2)',
|
||||
'is not defined in the preset': 'не определена в предустановке',
|
||||
'not defined in the preset': 'не определена в предустановке',
|
||||
'— no keyset —': '— нет набора ключей —',
|
||||
'KIc and KID must carry the same keyset number (TS 102 225 A.2)': 'KIc и KID должны содержать один и тот же номер keyset (TS 102 225 A.2)',
|
||||
'is not defined in the preset': 'не определён в пресете',
|
||||
'not defined in the preset': 'не определён в пресете',
|
||||
'— no keyset —': '— нет keyset —',
|
||||
'counter': 'счётчик',
|
||||
'Define at least one keyset (KIc/KID and their keys)': 'Определите хотя бы один набор ключей (KIc/KID и их ключи)',
|
||||
'Define at least one keyset (KIc/KID and their keys)': 'Определите хотя бы один keyset (KIc/KID и его ключи)',
|
||||
'Each keyset needs KIc, KID and both keys': 'Каждому набору нужны KIc, KID и оба ключа',
|
||||
'Key version 00 means "no security" and is not a preset keyset': 'Версия ключа 00 означает «без защиты» и не является набором ключей предустановки',
|
||||
'Duplicate key version in the keysets': 'Дублирующаяся версия ключа в наборах',
|
||||
'Select a card preset - its keysets define the key versions': 'Выберите предустановку карты — её наборы ключей определяют версии ключей',
|
||||
'SCP80 counters saved to the preset.': 'Счётчики SCP80 сохранены в предустановке.',
|
||||
'Keyset 00 means "no security" and is not a preset keyset': 'keyset 00 означает «без защиты» и не является keyset пресета',
|
||||
'Duplicate keyset number in the keysets': 'Повторяющийся номер keyset',
|
||||
'Select a card preset - its keysets define the keyset numbers': 'Выберите пресет карты — его keysets задают номера keyset',
|
||||
'SCP80 counters saved to the preset.': 'Счётчики SCP80 сохранены в пресете.',
|
||||
'counter not tracked (SPI1 has no counter check)': 'счётчик не отслеживается (в SPI1 нет проверки счётчика)',
|
||||
'TAR probe': 'TAR-сканер',
|
||||
'Sends one secured packet per selected TAR (TS 101 220 Annex D, Table D.1) carrying a harmless command, and classifies the card\'s answer - which OTA applications it has registered. Every packet the card accepts consumes a counter, which is saved to the preset.': 'Отправляет по одному защищённому пакету на каждый выбранный TAR (TS 101 220 Annex D, Table D.1) с безобидной командой и классифицирует ответ карты — какие OTA-приложения на ней зарегистрированы. Каждый принятый картой пакет расходует счётчик, который сохраняется в предустановке.',
|
||||
'Sends one secured packet per selected TAR (TS 101 220 Annex D, Table D.1) carrying a harmless command, and classifies the card\'s answer - which OTA applications it has registered. Every packet the card accepts consumes a counter, which is saved to the preset.': 'Отправляет по одному защищённому пакету на каждый выбранный TAR (TS 101 220 Annex D, Table D.1) с безобидной командой и классифицирует ответ карты — какие OTA-приложения на ней зарегистрированы. Каждый принятый картой пакет расходует счётчик, который сохраняется в пресете.',
|
||||
'TARs to probe — TS 101 220 V18.3.0 Annex D, Table D.1': 'TAR для пробы — TS 101 220 V18.3.0 Annex D, Table D.1',
|
||||
'Select all': 'Выбрать все',
|
||||
'Select none': 'Снять все',
|
||||
@@ -19803,7 +19802,7 @@ const LANG_RU = {
|
||||
'Probe TARs (registered OTA applications)': 'Проба TAR (зарегистрированные OTA-приложения)',
|
||||
'Probe APDU (hex)': 'APDU пробы (hex)',
|
||||
'TARs to probe (hex, one per line; empty = all standard TARs of TS 101 220 Annex D)': 'TAR для пробы (hex, по одному в строке; пусто = все стандартные TAR из TS 101 220 Annex D)',
|
||||
'One secured packet per TAR: each packet the card accepts consumes a counter, which is saved to the preset.': 'Один защищённый пакет на каждый TAR: каждый принятый картой пакет расходует счётчик, который сохраняется в предустановке.',
|
||||
'One secured packet per TAR: each packet the card accepts consumes a counter, which is saved to the preset.': 'Один защищённый пакет на каждый TAR: каждый принятый картой пакет расходует счётчик, который сохраняется в пресете.',
|
||||
'Specify the probe APDU': 'Укажите APDU пробы',
|
||||
'Registered applications:': 'Зарегистрированные приложения:',
|
||||
'Application': 'Приложение',
|
||||
@@ -19815,14 +19814,14 @@ const LANG_RU = {
|
||||
'accepted, no PoR': 'принято, нет PoR',
|
||||
'No card presets defined — the SCP80/RAM views have no keys for the equipped card. Add one in the Cards tab.': 'Предустановки карт не заданы — в представлениях SCP80/RAM нет ключей для подключённой карты. Добавьте её на вкладке «Карты».',
|
||||
'The equipped card': 'Подключённая карта',
|
||||
'does not match any card preset — the selected preset may belong to another card. Add it in the Cards tab (From card).': 'не совпадает ни с одной предустановкой — выбранная предустановка может принадлежать другой карте. Добавьте её на вкладке «Карты» (кнопка «С карты»).',
|
||||
"The selected preset is not the equipped card's": 'Выбранная предустановка не принадлежит подключённой карте',
|
||||
'does not match any card preset — the selected preset may belong to another card. Add it in the Cards tab (From card).': 'не совпадает ни с одним пресетом — выбранный пресет может принадлежать другой карте. Добавьте его на вкладке «Карты» (кнопка «С карты»).',
|
||||
"The selected preset is not the equipped card's": 'Выбранный пресет не принадлежит подключённой карте',
|
||||
'check that its keys and counters belong to this card.': 'проверьте, что её ключи и счётчики относятся к этой карте.',
|
||||
'Key version (kvn, optional)': 'Версия ключа (kvn, опционально)',
|
||||
"empty = the preset's first keyset": 'пусто = первый набор ключей предустановки',
|
||||
'Keysets (key version: KIc/KID — counter)': 'Наборы ключей (версия: KIc/KID — счётчик)',
|
||||
'KIc and KID must carry the same key version (their high nibble) in the range 01-0F — TS 102 225 A.2; each key version has its own counter on the card (Annex A.1).': 'KIc и KID должны содержать одну и ту же версию ключа (старший ниббл) в диапазоне 01-0F — TS 102 225 A.2; у каждой версии ключа свой счётчик на карте (Annex A.1).',
|
||||
'Card presets are stored on the server. JSON export/import buttons are below the card list; a preset carries one keyset per key version, each with its own counter.': 'Предустановки карт хранятся на сервере. Кнопки экспорта/импорта JSON — под списком карт; предустановка содержит один набор ключей на версию, у каждой свой счётчик.',
|
||||
'Keyset number (optional)': 'Номер keyset (опционально)',
|
||||
"empty = the preset's first keyset": 'пусто = первый keyset пресета',
|
||||
'Keysets (number: KIc/KID — counter)': 'Keysets (номер: KIc/KID — счётчик)',
|
||||
'KIc and KID must carry the same keyset number (their high nibble) in the range 01-0F — TS 102 225 A.2; each keyset has its own counter on the card (Annex A.1).': 'KIc и KID должны содержать один и тот же номер keyset (старший ниббл) в диапазоне 01-0F — TS 102 225 A.2; у каждого keyset свой счётчик на карте (Annex A.1).',
|
||||
'Card presets are stored on the server. JSON export/import buttons are below the card list; a preset carries several keysets, each with its own number and counter.': 'Пресеты карт хранятся на сервере. Кнопки экспорта/импорта JSON — под списком карт; пресет содержит несколько keysets, у каждого свой номер и счётчик.',
|
||||
'Save error': 'Ошибка сохранения',
|
||||
'Delete error': 'Ошибка удаления',
|
||||
'Counter not saved:': 'Счётчик не сохранён:',
|
||||
@@ -19883,9 +19882,9 @@ const LANG_RU = {
|
||||
'Show link events (Channel status ENVELOPEs, TS 102 223 7.5.11)': 'Показывать события канала (ENVELOPE Channel status, TS 102 223 7.5.11)',
|
||||
'ADM verified': 'ADM подтверждён',
|
||||
'ADM not verified': 'ADM не подтверждён',
|
||||
'ADM key in the card preset — verified': 'Ключ ADM в предустановке — подтверждён',
|
||||
'ADM key in the card preset — not verified': 'Ключ ADM в предустановке — не подтверждён',
|
||||
'ADM key in the card preset — click to verify': 'Ключ ADM в предустановке — нажмите для проверки',
|
||||
'ADM key in the card preset — verified': 'Ключ ADM в пресете — подтверждён',
|
||||
'ADM key in the card preset — not verified': 'Ключ ADM в пресете — не подтверждён',
|
||||
'ADM key in the card preset — click to verify': 'Ключ ADM в пресете — нажмите для проверки',
|
||||
'Verify ADM': 'Проверить ADM',
|
||||
'Verifying ADM...': 'Проверка ADM...',
|
||||
'ADM verification failed': 'Проверка ADM не удалась',
|
||||
@@ -19895,8 +19894,8 @@ const LANG_RU = {
|
||||
'This is the last attempt before the ADM is blocked. Try again?': 'Это последняя попытка — неверный ключ заблокирует ADM. Попробовать снова?',
|
||||
'ADM is blocked': 'ADM заблокирован',
|
||||
'ADM is blocked — unblock the card to try again': 'ADM заблокирован — разблокируйте карту, чтобы повторить',
|
||||
'Verified — no ADM key in the card preset': 'Подтверждён — ключа ADM в предустановке нет',
|
||||
'No ADM key in the card preset — not verified': 'Ключа ADM в предустановке нет — не подтверждён',
|
||||
'Verified — no ADM key in the card preset': 'Подтверждён — ключа ADM в пресете нет',
|
||||
'No ADM key in the card preset — not verified': 'Ключа ADM в пресете нет — не подтверждён',
|
||||
'SCP80 preset complete': 'Предустановка SCP80 заполнена',
|
||||
'SCP81 preset complete': 'Предустановка SCP81 заполнена',
|
||||
'Network state': 'Сетевое состояние',
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
const CACHE = 'simple-v339';
|
||||
const CACHE = 'simple-v340';
|
||||
const URLS = [
|
||||
'index.html',
|
||||
'help.html',
|
||||
|
||||
@@ -38,13 +38,13 @@ const PRESET = {
|
||||
],
|
||||
};
|
||||
|
||||
test('spKeysetKvnOf reads the key version from the high nibble', () => {
|
||||
test('spKeysetKvnOf reads the keyset number from the high nibble', () => {
|
||||
assert.strictEqual(spKeysetKvnOf('15'), 1);
|
||||
assert.strictEqual(spKeysetKvnOf('29'), 2);
|
||||
assert.strictEqual(spKeysetKvnOf('3A'), 3);
|
||||
assert.strictEqual(spKeysetKvnOf('05'), 0);
|
||||
assert.strictEqual(spKeysetKvnOf('ff'), 15);
|
||||
// not a byte: no key version
|
||||
// not a byte: no keyset number
|
||||
assert.strictEqual(spKeysetKvnOf(''), null);
|
||||
assert.strictEqual(spKeysetKvnOf('5'), null);
|
||||
assert.strictEqual(spKeysetKvnOf('ZZ'), null);
|
||||
@@ -60,15 +60,15 @@ test('spKeysetList converts a v3.8.0 flat preset into one keyset', () => {
|
||||
assert.deepStrictEqual(spKeysetList({}), []);
|
||||
});
|
||||
|
||||
test('spKeysetFor finds a keyset by key version', () => {
|
||||
test('spKeysetFor finds a keyset by number', () => {
|
||||
assert.strictEqual(spKeysetFor(PRESET, 1).cntr, '0000000001');
|
||||
assert.strictEqual(spKeysetFor(PRESET, 2).cntr, '0000000005');
|
||||
assert.strictEqual(spKeysetFor(PRESET, 3), null);
|
||||
assert.strictEqual(spKeysetFor(null, 1), null);
|
||||
});
|
||||
|
||||
test('spKeysetCheck enforces the key-version rules', () => {
|
||||
// a defined key version passes
|
||||
test('spKeysetCheck enforces the keyset-number rules', () => {
|
||||
// a defined keyset number passes
|
||||
assert.strictEqual(spKeysetCheck(PRESET, '15', '15'), '');
|
||||
assert.strictEqual(spKeysetCheck(PRESET, '29', '29'), '');
|
||||
// '00'/'00' means no security: no keyset needed (TS 102 225 A.2)
|
||||
@@ -76,8 +76,8 @@ test('spKeysetCheck enforces the key-version rules', () => {
|
||||
// ... and one zero byte with a defined version passes too
|
||||
assert.strictEqual(spKeysetCheck(PRESET, '15', '00'), '');
|
||||
// KIc/KID versions must agree when both are non-zero (A.2)
|
||||
assert.match(spKeysetCheck(PRESET, '15', '25'), /same key version/);
|
||||
// the key version must be defined in the preset
|
||||
assert.match(spKeysetCheck(PRESET, '15', '25'), /same keyset number/);
|
||||
// the keyset number must be defined in the preset
|
||||
assert.match(spKeysetCheck(PRESET, '35', '35'), /3 .*not defined/);
|
||||
// one zero byte: the other byte's version applies (keyset 2 is defined)
|
||||
assert.strictEqual(spKeysetCheck(PRESET, '00', '25'), '');
|
||||
@@ -90,7 +90,7 @@ test('spKeysetCheck enforces the key-version rules', () => {
|
||||
assert.match(spKeysetCheck(PRESET, '15', 'ZZ'), /KID/);
|
||||
});
|
||||
|
||||
test('spKeysetOptionsHtml lists the keysets and marks an undefined version', () => {
|
||||
test('spKeysetOptionsHtml lists the keysets and marks an undefined number', () => {
|
||||
const html1 = spKeysetOptionsHtml(PRESET, '15', '15');
|
||||
assert.match(html1, /value="1" selected/);
|
||||
assert.match(html1, /value="2"/);
|
||||
@@ -102,7 +102,7 @@ test('spKeysetOptionsHtml lists the keysets and marks an undefined version', ()
|
||||
// a preset without keysets: the form's version shows as undefined
|
||||
const html3 = spKeysetOptionsHtml({ keysets: [] }, '15', '15');
|
||||
assert.match(html3, /not defined in the preset/);
|
||||
// no key version in the form and no keysets at all
|
||||
// no keyset number in the form and no keysets at all
|
||||
assert.match(spKeysetOptionsHtml({}, '', ''), /no keyset/);
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user