v3.28.2: the outfile ADM encoding fix
The ADM column carries the key's hex value (the length varies, 4-16 hex
digits) - the form the preset store and /api/verify-adm use (pySim pads
it with 'f' to the 8 CHV bytes). The parser treated it as an ASCII
string hex-encoded and either mangled it (3632333239463337 -> 62329F37,
a wrong ADM that would fail VERIFY and consume an attempt) or dropped it
(CFA3DD9C6B7F0F8C - the live report with AM0027_alfa_dsa.out).
- The ADM is stored as-is: non-hex stripped, uppercased, [0-9A-F]{4,16};
a non-hex or too-short value is dropped with "ADM is not 4-16 hex
digits".
- _ascii_from_hex() is gone (its only user was the ADM column).
- Docs: help EN/RU, api.md and the repo AGENTS now say "the key's hex
value, 4-16 hex digits (the length varies)".
Note: presets imported from a BAP-style file before this fix hold the
mangled ADM - re-import or correct them.
Tests: the BAP fixture (the hex as-is), the AM0027 shape, a
short/lowercase value and the drop cases. Both real samples verified
(BAP 3632333239463337, AM0027 CFA3DD9C6B7F0F8C; the ALFA sample has no
ADM column and is unchanged). 915 frontend + 925 Python; sw cache
simple-v440.
This commit is contained in:
+3
-2
@@ -344,8 +344,9 @@ into a preview.
|
||||
Recognised columns (case-insensitive): `ICCID` (the digits + odd-nibble F
|
||||
pad, the plain digits, spaced digits or the raw nibble-swapped EF hex all
|
||||
normalise to the same value), `PIN1`/`PIN2`, `PUK1`/`PUK2` (4–8 digits; an
|
||||
invalid value is dropped and reported, the card is kept), `ADM` (the ASCII
|
||||
string, hex-encoded — `3632333239463337` → `62329F37`), `PSK` (the SCP81 key,
|
||||
invalid value is dropped and reported, the card is kept), `ADM` (the key's
|
||||
**hex value**, 4–16 hex digits — the length varies; this is the form the preset
|
||||
store and `/api/verify-adm` use), `PSK` (the SCP81 key,
|
||||
exactly 32 hex chars) and `KICn`/`KIDn` — one keyset per numbered pair, up to
|
||||
15. The **KIc/KID bytes are never in the file**: the keyset number is the
|
||||
column number (`KIC2` → keyset 2) and the algorithm nibble follows the key
|
||||
|
||||
@@ -369,7 +369,7 @@
|
||||
</tbody>
|
||||
</table>
|
||||
<p class="text-sm mb-3">Столбец <strong>SCP81</strong> показывает, задана ли в пресете рабочая пара PSK: <strong>✓</strong> (зелёный) — идентификатор и ключ заданы; <strong>⚠</strong> (жёлтый) — задано только одно из двух, и слушатель такой пресет игнорирует; <strong>—</strong> — PSK нет. Идентификатор и ключ PSK задаются вместе. Кнопка <strong>Добавить пресет</strong> открывает редактор с новым пресетом; <strong>Редактировать</strong> загружает пресет строки в редактор, поэтому поля можно менять без повторного ввода карты; <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 вручную, сохраняется при отправке - счётчик, ключи и MSL этого TAR по-прежнему берутся из пресета; SPI1 берётся из MSL, а меньшее значение даёт предупреждение (сервер тоже возвращает его как <code class="font-mono text-sm">warning</code>); изменения сразу передаются работающему слушателю SCP81.</p>
|
||||
<p class="text-sm mb-3"><strong>Импорт outfile</strong> загружает сразу много карт из <em>outfile</em> поставщика карт — текстового дампа инструмента персонализации: шапка с баннером и параметрами, затем разделённая пробелами <strong>строка колонок</strong> (среди токенов есть <code class="font-mono text-sm">ICCID</code>; первый токен может нести префикс вида <code class="font-mono text-sm">var_out:</code>) и по одной строке на карту. Всё до строки колонок и все неиспользуемые колонки игнорируются. Разбираются <code class="font-mono text-sm">ICCID</code> (цифры, цифры с добивочным <code class="font-mono text-sm">F</code> до чётного числа нибблов, цифры с разделителями или сырой hex EF — всё приводится к одной форме), <code class="font-mono text-sm">PIN1</code>/<code class="font-mono text-sm">PIN2</code>/<code class="font-mono text-sm">PUK1</code>/<code class="font-mono text-sm">PUK2</code>, <code class="font-mono text-sm">ADM</code> (строка ASCII, закодированная hex — <code class="font-mono text-sm">3632333239463337</code> это <code class="font-mono text-sm">62329F37</code>), <code class="font-mono text-sm">PSK</code> (16 байт; необязательная колонка <code class="font-mono text-sm">PSKID</code>/<code class="font-mono text-sm">PSK_ID</code> задаёт её идентификатор, по умолчанию берётся ICCID карты) и нумерованные пары ключей <code class="font-mono text-sm">KICn</code>/<code class="font-mono text-sm">KIDn</code> — по одному keyset на пару, до 15. <strong>Байтов KIc/KID в файле нет</strong>: номер keyset — это номер колонки (<code class="font-mono text-sm">KIC2</code> → keyset 2), а ниббл алгоритма определяется размером ключа — 8 байт DES, 24 байта 3DES3, а для <strong>16 байт</strong> — выбором в предпросмотре: <strong>3DES2</strong> (по умолчанию) или <strong>AES</strong>; любой другой размер пропускает этот keyset с пометкой. TAR также не берутся из файла: предпросмотр показывает таблицу TAR, которую получат импортируемые карты (по умолчанию три ролевые записи с TAR по спецификации и MSL 16; их можно править, кнопка <strong>+ TAR</strong> добавляет записи), и один <strong>keyset по умолчанию</strong>, применяемый ко всем записям TAR — он обязателен для карты с несколькими keysets (ненулевой MSL должен называть keyset). <strong>Общее имя</strong> применяется ко всем импортируемым картам (пустое — берётся ICCID); <strong>режим импорта</strong> выбирает <strong>Дополнить</strong> (по умолчанию — существующие пресеты сохраняются, дубли ICCID пропускаются и перечисляются) или <strong>Заменить</strong> (хранилище сначала очищается). Предпросмотр перечисляет разобранные карты (ICCID, keysets, найденные коды) и каждую пропущенную строку/ключ с причиной — до того, как что-либо сохранено.</p>
|
||||
<p class="text-sm mb-3"><strong>Импорт outfile</strong> загружает сразу много карт из <em>outfile</em> поставщика карт — текстового дампа инструмента персонализации: шапка с баннером и параметрами, затем разделённая пробелами <strong>строка колонок</strong> (среди токенов есть <code class="font-mono text-sm">ICCID</code>; первый токен может нести префикс вида <code class="font-mono text-sm">var_out:</code>) и по одной строке на карту. Всё до строки колонок и все неиспользуемые колонки игнорируются. Разбираются <code class="font-mono text-sm">ICCID</code> (цифры, цифры с добивочным <code class="font-mono text-sm">F</code> до чётного числа нибблов, цифры с разделителями или сырой hex EF — всё приводится к одной форме), <code class="font-mono text-sm">PIN1</code>/<code class="font-mono text-sm">PIN2</code>/<code class="font-mono text-sm">PUK1</code>/<code class="font-mono text-sm">PUK2</code>, <code class="font-mono text-sm">ADM</code> (hex-значение ключа, 4–16 hex-цифр — длина различается, например <code class="font-mono text-sm">3632333239463337</code>), <code class="font-mono text-sm">PSK</code> (16 байт; необязательная колонка <code class="font-mono text-sm">PSKID</code>/<code class="font-mono text-sm">PSK_ID</code> задаёт её идентификатор, по умолчанию берётся ICCID карты) и нумерованные пары ключей <code class="font-mono text-sm">KICn</code>/<code class="font-mono text-sm">KIDn</code> — по одному keyset на пару, до 15. <strong>Байтов KIc/KID в файле нет</strong>: номер keyset — это номер колонки (<code class="font-mono text-sm">KIC2</code> → keyset 2), а ниббл алгоритма определяется размером ключа — 8 байт DES, 24 байта 3DES3, а для <strong>16 байт</strong> — выбором в предпросмотре: <strong>3DES2</strong> (по умолчанию) или <strong>AES</strong>; любой другой размер пропускает этот keyset с пометкой. TAR также не берутся из файла: предпросмотр показывает таблицу TAR, которую получат импортируемые карты (по умолчанию три ролевые записи с TAR по спецификации и MSL 16; их можно править, кнопка <strong>+ TAR</strong> добавляет записи), и один <strong>keyset по умолчанию</strong>, применяемый ко всем записям TAR — он обязателен для карты с несколькими keysets (ненулевой MSL должен называть keyset). <strong>Общее имя</strong> применяется ко всем импортируемым картам (пустое — берётся ICCID); <strong>режим импорта</strong> выбирает <strong>Дополнить</strong> (по умолчанию — существующие пресеты сохраняются, дубли ICCID пропускаются и перечисляются) или <strong>Заменить</strong> (хранилище сначала очищается). Предпросмотр перечисляет разобранные карты (ICCID, keysets, найденные коды) и каждую пропущенную строку/ключ с причиной — до того, как что-либо сохранено.</p>
|
||||
</section>
|
||||
|
||||
<section class="mb-10">
|
||||
|
||||
+1
-1
@@ -368,7 +368,7 @@
|
||||
</tbody>
|
||||
</table>
|
||||
<p class="text-sm mb-3">The <strong>SCP81</strong> column shows whether the preset supplies a usable PSK pair: <strong>✓</strong> (green) — identity and key set; <strong>⚠</strong> (amber) — only one of the two, so the listener ignores the preset; <strong>—</strong> — no PSK. PSK identity and key must be set together. <strong>Add preset</strong> opens the editor with a fresh preset; <strong>Edit</strong> loads the row's preset into it so fields can be changed without re-entering the card; <strong>Save</strong> stores it and returns to the list, <strong>Cancel</strong> discards the edits; <strong>Remove</strong> deletes the row (list only). Presets can be shared with <strong>Export to file</strong> and restored with <strong>Import from file</strong>. The selected card preset auto-fills the Secured Packet form and sets the TAR for the operation (RAM/GP → ISD TAR, SIM RFM → UICC RFM TAR, USIM RFM → ADF RFM TAR); a TAR typed into the Secured Packet form by hand is kept when sending - the counter, keys and the TAR's MSL are still re-read from the preset; the SPI1 is seeded from the TAR's MSL and a lower value warns (the server returns it as a <code class="font-mono text-sm">warning</code> too); edits are pushed into a running SCP81 listener automatically.</p>
|
||||
<p class="text-sm mb-3"><strong>Import outfile</strong> imports many cards at once from a card vendor's <em>outfile</em> — the personalization tool's plain-text dump: a banner and a configuration header, then a whitespace-separated <strong>column line</strong> (the tokens include <code class="font-mono text-sm">ICCID</code>; the first token may carry a <code class="font-mono text-sm">var_out:</code>-style prefix) and one row per card. Everything before the column line and every unused column is ignored. The parser reads <code class="font-mono text-sm">ICCID</code> (digits, the digits with an odd-nibble <code class="font-mono text-sm">F</code> pad, spaced digits or the raw EF hex — all normalised to one form), <code class="font-mono text-sm">PIN1</code>/<code class="font-mono text-sm">PIN2</code>/<code class="font-mono text-sm">PUK1</code>/<code class="font-mono text-sm">PUK2</code>, <code class="font-mono text-sm">ADM</code> (the ASCII string, hex-encoded — <code class="font-mono text-sm">3632333239463337</code> is <code class="font-mono text-sm">62329F37</code>), <code class="font-mono text-sm">PSK</code> (16 bytes; an optional <code class="font-mono text-sm">PSKID</code>/<code class="font-mono text-sm">PSK_ID</code> names its identity, defaulting to the card's ICCID) and the numbered key pairs <code class="font-mono text-sm">KICn</code>/<code class="font-mono text-sm">KIDn</code> — one keyset per pair, up to 15. The <strong>KIc/KID bytes are never in the file</strong>: the keyset number is the column number (<code class="font-mono text-sm">KIC2</code> → keyset 2) and the algorithm nibble follows the key size — 8 bytes DES, 24 bytes 3DES3, and for <strong>16 bytes</strong> the preview's choice, <strong>3DES2</strong> (default) or <strong>AES</strong>; any other size skips that keyset with a note. The TARs never come from the file either: the preview shows the TAR table the imported cards get (the three role entries with their spec TARs and MSL 16 by default, editable, with <strong>+ TAR</strong> for extra entries) and one <strong>default keyset</strong> applied to every TAR entry — required for a card with several keysets (a non-zero MSL must name a keyset). A <strong>common name</strong> is applied to every imported card (empty falls back to the ICCID); <strong>Import mode</strong> chooses <strong>Merge</strong> (default — existing presets are kept, duplicate ICCIDs are skipped and reported) or <strong>Replace</strong> (the store is wiped first). The preview lists the parsed cards (ICCID, keysets, the codes found) and every skipped row/key with its reason before anything is stored.</p>
|
||||
<p class="text-sm mb-3"><strong>Import outfile</strong> imports many cards at once from a card vendor's <em>outfile</em> — the personalization tool's plain-text dump: a banner and a configuration header, then a whitespace-separated <strong>column line</strong> (the tokens include <code class="font-mono text-sm">ICCID</code>; the first token may carry a <code class="font-mono text-sm">var_out:</code>-style prefix) and one row per card. Everything before the column line and every unused column is ignored. The parser reads <code class="font-mono text-sm">ICCID</code> (digits, the digits with an odd-nibble <code class="font-mono text-sm">F</code> pad, spaced digits or the raw EF hex — all normalised to one form), <code class="font-mono text-sm">PIN1</code>/<code class="font-mono text-sm">PIN2</code>/<code class="font-mono text-sm">PUK1</code>/<code class="font-mono text-sm">PUK2</code>, <code class="font-mono text-sm">ADM</code> (the key's hex value, 4–16 hex digits — the length varies, e.g. <code class="font-mono text-sm">3632333239463337</code>), <code class="font-mono text-sm">PSK</code> (16 bytes; an optional <code class="font-mono text-sm">PSKID</code>/<code class="font-mono text-sm">PSK_ID</code> names its identity, defaulting to the card's ICCID) and the numbered key pairs <code class="font-mono text-sm">KICn</code>/<code class="font-mono text-sm">KIDn</code> — one keyset per pair, up to 15. The <strong>KIc/KID bytes are never in the file</strong>: the keyset number is the column number (<code class="font-mono text-sm">KIC2</code> → keyset 2) and the algorithm nibble follows the key size — 8 bytes DES, 24 bytes 3DES3, and for <strong>16 bytes</strong> the preview's choice, <strong>3DES2</strong> (default) or <strong>AES</strong>; any other size skips that keyset with a note. The TARs never come from the file either: the preview shows the TAR table the imported cards get (the three role entries with their spec TARs and MSL 16 by default, editable, with <strong>+ TAR</strong> for extra entries) and one <strong>default keyset</strong> applied to every TAR entry — required for a card with several keysets (a non-zero MSL must name a keyset). A <strong>common name</strong> is applied to every imported card (empty falls back to the ICCID); <strong>Import mode</strong> chooses <strong>Merge</strong> (default — existing presets are kept, duplicate ICCIDs are skipped and reported) or <strong>Replace</strong> (the store is wiped first). The preview lists the parsed cards (ICCID, keysets, the codes found) and every skipped row/key with its reason before anything is stored.</p>
|
||||
</section>
|
||||
|
||||
<section class="mb-10">
|
||||
|
||||
+1
-1
@@ -1910,7 +1910,7 @@
|
||||
// ===== Version =====
|
||||
// Single source of truth for the PWA version: shown in the header and used
|
||||
// by the server version check in pysimConnect().
|
||||
const SIMPLE_VERSION = '3.28.1';
|
||||
const SIMPLE_VERSION = '3.28.2';
|
||||
document.getElementById('app-version').textContent = 'v' + SIMPLE_VERSION;
|
||||
|
||||
// ===== view history (the browser Back/Forward buttons) =====
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
const CACHE = 'simple-v439';
|
||||
const CACHE = 'simple-v440';
|
||||
const URLS = [
|
||||
'index.html',
|
||||
'help.html',
|
||||
|
||||
+1
-1
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
||||
|
||||
[project]
|
||||
name = "pysim-simple-server"
|
||||
version = "3.28.1"
|
||||
version = "3.28.2"
|
||||
description = "HTTP REST server wrapping pysim for the SIMple PWA"
|
||||
requires-python = ">=3.8"
|
||||
# pysim is a git-only dependency installed explicitly by setup.bat/setup.sh.
|
||||
|
||||
@@ -8,8 +8,9 @@ prefix) - and continues with one whitespace-separated row per card. The
|
||||
header and every column the presets do not use are ignored.
|
||||
|
||||
Recognised columns (case-insensitive): ``ICCID``, ``PIN1``/``PIN2``,
|
||||
``PUK1``/``PUK2``, ``ADM`` (the ASCII string, hex-encoded - the vendors print
|
||||
``3632333239463337`` for ``62329F37``), ``PSK`` (the SCP81 key, 16 bytes; an
|
||||
``PUK1``/``PUK2``, ``ADM`` (the key's hex value - the length varies, e.g. 8 or
|
||||
16 hex digits; this is the form the preset store and ``/api/verify-adm`` use,
|
||||
where pySim pads it with 'f' to the 8 CHV bytes), ``PSK`` (the SCP81 key, 16 bytes; an
|
||||
optional ``PSKID``/``PSK_ID`` column names its identity, defaulting to the
|
||||
card's ICCID) and ``KICn``/``KIDn`` - one keyset per numbered pair, up to 15.
|
||||
Everything else (``IMSI*``, ``EKI``, ``KIKn`` - the key-management key, no
|
||||
@@ -175,14 +176,6 @@ def _hex_value(tok):
|
||||
return v if v and len(v) % 2 == 0 else ''
|
||||
|
||||
|
||||
def _ascii_from_hex(v):
|
||||
"""The ADM column: an ASCII string hex-encoded. '' when it is not."""
|
||||
try:
|
||||
return bytes.fromhex(v).decode('ascii')
|
||||
except (ValueError, UnicodeDecodeError):
|
||||
return ''
|
||||
|
||||
|
||||
def parse_outfile(text, name='', algorithm16='3des2', tars=None, tar_keyset=None):
|
||||
"""Parse an outfile into preset dicts (the store's shape).
|
||||
|
||||
@@ -273,11 +266,13 @@ def parse_outfile(text, name='', algorithm16='3des2', tars=None, tar_keyset=None
|
||||
% (row_no, col, raw))
|
||||
i = idx['ADM']
|
||||
if 0 <= i < len(toks) and toks[i]:
|
||||
adm = _ascii_from_hex(_hex_value(toks[i]))
|
||||
if re.fullmatch(r'[0-9A-Fa-f]{4,16}', adm or ''):
|
||||
entry['adm'] = adm.upper()
|
||||
# The ADM column carries the key's hex value itself (the length
|
||||
# varies) - the form the preset store and /api/verify-adm use.
|
||||
adm = re.sub(r'[^0-9A-Fa-f]', '', str(toks[i])).upper()
|
||||
if re.fullmatch(r'[0-9A-F]{4,16}', adm):
|
||||
entry['adm'] = adm
|
||||
else:
|
||||
skip('line %d: ADM is not an ASCII-hex key - dropped' % row_no)
|
||||
skip('line %d: ADM is not 4-16 hex digits - dropped' % row_no)
|
||||
i = idx['PSK']
|
||||
if 0 <= i < len(toks) and toks[i]:
|
||||
psk = _hex_value(toks[i])
|
||||
|
||||
@@ -37,7 +37,7 @@ from osmocom.tlv import BER_TLV_IE
|
||||
from osmocom.utils import rpad
|
||||
|
||||
|
||||
VERSION = '3.28.1'
|
||||
VERSION = '3.28.2'
|
||||
|
||||
MAX_ENVELOPE_SEGMENTS = 5 # max SMS segments for outgoing C-APDU in ENVELOPE
|
||||
|
||||
|
||||
+27
-3
@@ -139,7 +139,7 @@ class BapShapeTests(unittest.TestCase):
|
||||
self.assertEqual(p['pin2'], '4321')
|
||||
self.assertEqual(p['puk1'], '12345678')
|
||||
self.assertEqual(p['puk2'], '87654321')
|
||||
self.assertEqual(p['adm'], '62329F37') # the ASCII-hex decode
|
||||
self.assertEqual(p['adm'], '3632333239463337') # the key's hex value as-is
|
||||
# EKI, KIK and the IMSI columns are ignored - one keyset only
|
||||
self.assertEqual(len(p['keysets']), 1)
|
||||
self.assertEqual(p['keysets'][0]['kicKey'], K16A)
|
||||
@@ -233,11 +233,35 @@ class OptionTests(unittest.TestCase):
|
||||
self.assertEqual(len(res['presets']), 1)
|
||||
self.assertEqual(res['presets'][0]['pin1'], '1234')
|
||||
self.assertNotIn('adm', res['presets'][0])
|
||||
# a present ADM is decoded from its ASCII-hex form
|
||||
# the ADM column is the key's hex value, stored as-is (v3.28.2): the
|
||||
# length varies, and the value is never ASCII-decoded
|
||||
text = ('ICCID KIC1 KID1 ADM\n' + DIGITS_F + ' ' + K16A + ' ' + K16B +
|
||||
' 3632333239463337\n')
|
||||
res = outfile.parse_outfile(text)
|
||||
self.assertEqual(res['presets'][0]['adm'], '62329F37')
|
||||
self.assertEqual(res['presets'][0]['adm'], '3632333239463337')
|
||||
# ...an 8-byte binary-looking value (the AM0027 sample's shape)
|
||||
text = ('ICCID KIC1 KID1 ADM\n' + DIGITS_F + ' ' + K16A + ' ' + K16B +
|
||||
' CFA3DD9C6B7F0F8C\n')
|
||||
res = outfile.parse_outfile(text)
|
||||
self.assertEqual(res['presets'][0]['adm'], 'CFA3DD9C6B7F0F8C')
|
||||
# ...a shorter key (4 hex digits) and the lowercase form
|
||||
text = ('ICCID KIC1 KID1 ADM\n' + DIGITS_F + ' ' + K16A + ' ' + K16B +
|
||||
' 1a2b3c4d\n')
|
||||
res = outfile.parse_outfile(text)
|
||||
self.assertEqual(res['presets'][0]['adm'], '1A2B3C4D')
|
||||
# a non-hex or too-short ADM is dropped with its reason
|
||||
text = ('ICCID KIC1 KID1 ADM\n' + DIGITS_F + ' ' + K16A + ' ' + K16B +
|
||||
' ZZZZ\n')
|
||||
res = outfile.parse_outfile(text)
|
||||
self.assertNotIn('adm', res['presets'][0])
|
||||
self.assertTrue(any('ADM is not 4-16 hex digits' in s
|
||||
for s in res['report']['skipped']))
|
||||
text = ('ICCID KIC1 KID1 ADM\n' + DIGITS_F + ' ' + K16A + ' ' + K16B +
|
||||
' 123\n')
|
||||
res = outfile.parse_outfile(text)
|
||||
self.assertNotIn('adm', res['presets'][0])
|
||||
self.assertTrue(any('ADM is not 4-16 hex digits' in s
|
||||
for s in res['report']['skipped']))
|
||||
# a present but invalid PIN is dropped, the card kept
|
||||
text = ('ICCID KIC1 KID1 PIN1\n' + DIGITS_F + ' ' + K16A + ' ' + K16B + ' 12\n')
|
||||
res = outfile.parse_outfile(text)
|
||||
|
||||
Reference in New Issue
Block a user