fix: keep a hand-edited packet TAR in the SCP80 send (v3.6.21)
The plain "Send to Card" path refreshed the whole preset right before sending (spRefreshFromPreset -> cardsApply -> cardsApplyFields) and re-applied the preset's slot TAR - so a TAR typed for a push/link trigger, an applet OTA message or the HTTP OTA admin params was silently replaced by the preset's ISD TAR (the pack buttons for those sources deliberately leave the TAR manual; the help even said so). - spRefreshFromPreset(selId, keepTar): the plain send passes keepTar, so the form's TAR survives; the counter, keys and SPI are still re-read. - selecting a card preset, ramApplyCard and the chain packs still set the TAR; the RAM operations keep the preset's ISD TAR by design. - spParams()/getRamSpParams() normalise the TAR (strip/upper/pad to 6); only the preview padded before, a short TAR went to the server raw. - help EN/RU clarified, cards_counter/ram tests updated, sw simple-v294. 652 frontend / 499 Python green; version 3.6.21.
This commit is contained in:
@@ -254,7 +254,7 @@
|
||||
<p class="text-sm mb-2"><strong>Упаковать в Secured packet</strong> отправляет готовый payload на вкладку SCP80 для заполнения SPI/счётчика — там укажите TAR, который слушает SD (обычно TAR OTASD).</p>
|
||||
|
||||
<h4 id="push-ts102226" class="font-medium mb-1 mt-3">PUSH по TS 102 226 §9</h4>
|
||||
<p class="text-sm mb-2">Подвкладка <strong>«Триггер канала/линка»</strong> собирает запросы §9 тем же кодировщиком, что и строка <strong>PUSH</strong> в цепочке RAM/GP, показывает предпросмотр C-APDU, и предлагает <strong>«Упаковать в Secured packet»</strong> (TAR остаётся ручным: запрос адресован целевому приложению, а не Security Domain) и <strong>→ Расширенный скрипт</strong> (оборачивает каждый APDU в Command TLV <code class="font-mono text-sm">22</code> и добавляет их в конструктор Expanded Script).</p>
|
||||
<p class="text-sm mb-2">Подвкладка <strong>«Триггер канала/линка»</strong> собирает запросы §9 тем же кодировщиком, что и строка <strong>PUSH</strong> в цепочке RAM/GP, показывает предпросмотр C-APDU, и предлагает <strong>«Упаковать в Secured packet»</strong> (TAR остаётся ручным: запрос адресован целевому приложению, а не Security Domain, и введённый TAR сохраняется при отправке) и <strong>→ Расширенный скрипт</strong> (оборачивает каждый APDU в Command TLV <code class="font-mono text-sm">22</code> и добавляет их в конструктор Expanded Script).</p>
|
||||
<ul class="list-disc list-inside text-sm space-y-1 mb-3">
|
||||
<li><strong>Открытие BIP-канала</strong> (<code class="font-mono text-sm">01</code>) — собирается в режиме <strong>Fields</strong> из самых употребительных параметров OPEN CHANNEL (Stepping Stones §18.6.1): описание bearer, транспортный протокол (UDP или TCP, клиент UICC, удалённое соединение — только эти значения допускает команда) с портом назначения, адрес назначения, NAA/APN, размер буфера и alpha (флажок <em>прозрачный режим</em> отправляет нулевой alpha, т.е. без подтверждения пользователя); логин/пароль и остальное — в поле дополнительных TLV. <strong>Локальный адрес не отправляется никогда</strong> (§9.2.1 и руководство это запрещают). Режим <strong>Hex</strong> принимает сырые COMPREHENSION-TLV. Проактивную команду OPEN CHANNEL выдаёт само приложение (§9.1.1). SIMalliance Stepping Stones R7 §18.6 рекомендует отправлять его <strong>вместе с CAT_TP-запросом в одном сообщении</strong> (параметры канала — в <code class="font-mono text-sm">01</code>, порт CAT_TP — в <code class="font-mono text-sm">02</code>), поэтому в форме есть флажок <em>«Также запросить установление соединения CAT_TP (02)»</em>; пара передаётся одним скриптом команд (конкатенированные SMS, если больше 140 октетов).</li>
|
||||
<li><strong>Установление соединения CAT_TP</strong> (<code class="font-mono text-sm">02</code>) — порт назначения в transport level <code class="font-mono text-sm">3C</code> с типом протокола 00 (обязателен), необязательные buffer size <code class="font-mono text-sm">39</code> / данные идентификации <code class="font-mono text-sm">36</code>. Отдельно он полагается на предустановленные на карте умолчания канала/CAT_TP, против чего предостерегает руководство Stepping Stones; предпочтительна пара выше.</li>
|
||||
@@ -357,7 +357,7 @@
|
||||
<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); изменения сразу передаются работающему слушателю 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">
|
||||
|
||||
+2
-2
@@ -253,7 +253,7 @@
|
||||
<p class="text-sm mb-2"><strong>Pack into Secured packet</strong> sends the built payload to the SCP80 tab for SPI/counter filling — insert the TAR the SD listens on (typically the OTASD TAR) there.</p>
|
||||
|
||||
<h4 id="push-ts102226" class="font-medium mb-1 mt-3">TS 102 226 §9 PUSH</h4>
|
||||
<p class="text-sm mb-2">The <strong>Channel / link trigger</strong> sub-pill builds the §9 requests with the same encoder as the RAM/GP chain's <strong>PUSH</strong> row, previews the C-APDU(s), and offers <strong>Pack into Secured packet</strong> (the TAR stays manual: the request is addressed to the target application, not the Security Domain) and <strong>→ Expanded Script</strong> (wraps each APDU in the <code class="font-mono text-sm">22</code> Command TLV and appends them to the Expanded Script builder).</p>
|
||||
<p class="text-sm mb-2">The <strong>Channel / link trigger</strong> sub-pill builds the §9 requests with the same encoder as the RAM/GP chain's <strong>PUSH</strong> row, previews the C-APDU(s), and offers <strong>Pack into Secured packet</strong> (the TAR stays manual: the request is addressed to the target application, not the Security Domain, and a typed TAR is kept when sending) and <strong>→ Expanded Script</strong> (wraps each APDU in the <code class="font-mono text-sm">22</code> Command TLV and appends them to the Expanded Script builder).</p>
|
||||
<ul class="list-disc list-inside text-sm space-y-1 mb-3">
|
||||
<li><strong>BIP channel opening</strong> (<code class="font-mono text-sm">01</code>) — built in <strong>Fields</strong> mode from the most-used OPEN CHANNEL parameters (Stepping Stones §18.6.1): bearer description, transport protocol (UDP or TCP, UICC client, remote — the only values the command allows) with the destination port, destination address, NAA/APN, buffer size and alpha (a <em>transparent</em> checkbox sends the null alpha, i.e. no user confirmation); login/password and anything else go in the extra-TLVs field. The <strong>local address is never emitted</strong> (§9.2.1 and the guide both forbid it). <strong>Hex</strong> mode takes the raw COMPREHENSION-TLVs instead. The application issues the proactive OPEN CHANNEL itself (§9.1.1). SIMalliance Stepping Stones R7 §18.6 recommends sending it <strong>together with the CAT_TP request in the same message</strong> (the channel parameters ride in <code class="font-mono text-sm">01</code>, the CAT_TP port in <code class="font-mono text-sm">02</code>), so the form has an <em>Also request the CAT_TP link establishment (02)</em> checkbox; the pair is one concatenated command script (concatenated SMS when it exceeds 140 octets).</li>
|
||||
<li><strong>CAT_TP link establishment</strong> (<code class="font-mono text-sm">02</code>) — destination port in transport level <code class="font-mono text-sm">3C</code> with protocol type 00 (mandatory), optional buffer size <code class="font-mono text-sm">39</code> / identification data <code class="font-mono text-sm">36</code>. Standalone it relies on card-provisioned channel/CAT_TP defaults, which the Stepping Stones guide advises against; prefer the pair above.</li>
|
||||
@@ -356,7 +356,7 @@
|
||||
<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>
|
||||
</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>Edit</strong> loads a preset into the form (the Add button becomes <strong>Save</strong>; <strong>Cancel</strong> clears it) so fields can be changed without re-entering the card; <strong>Remove</strong> deletes the row. 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); edits are pushed into a running SCP81 listener automatically.</p>
|
||||
<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>Edit</strong> loads a preset into the form (the Add button becomes <strong>Save</strong>; <strong>Cancel</strong> clears it) so fields can be changed without re-entering the card; <strong>Remove</strong> deletes the row. 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 SPI are still re-read from the preset; edits are pushed into a running SCP81 listener automatically.</p>
|
||||
</section>
|
||||
|
||||
<section class="mb-10">
|
||||
|
||||
+20
-14
@@ -1711,7 +1711,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.6.20';
|
||||
const SIMPLE_VERSION = '3.6.21';
|
||||
document.getElementById('app-version').textContent = 'v' + SIMPLE_VERSION;
|
||||
|
||||
// ===== Tab switching =====
|
||||
@@ -4869,7 +4869,7 @@ function _genSpBuild() {
|
||||
const spi2 = parseInt(document.getElementById('sp-spi2-hex').value, 16);
|
||||
const kicHex = document.getElementById('sp-kic-hex').value;
|
||||
const kidHex = document.getElementById('sp-kid-hex').value;
|
||||
const tarHex = (document.getElementById('sp-tar').value || '').replace(/[^0-9a-fA-F]/g, '').padEnd(6, '0').slice(0, 6);
|
||||
const tarHex = (document.getElementById('sp-tar').value || '').replace(/[^0-9a-fA-F]/g, '').toUpperCase().padEnd(6, '0').slice(0, 6);
|
||||
const cntrHex = (document.getElementById('sp-cntr').value || '').replace(/[^0-9a-fA-F]/g, '').padStart(10, '0').slice(0, 10);
|
||||
const kicKeyHex = (document.getElementById('sp-kic-key').value || '').replace(/[^0-9a-fA-F]/g, '');
|
||||
const kidKeyHex = (document.getElementById('sp-kid-key').value || '').replace(/[^0-9a-fA-F]/g, '');
|
||||
@@ -8037,7 +8037,7 @@ function spParams() {
|
||||
spi2: document.getElementById('sp-spi2-hex').value,
|
||||
kic: document.getElementById('sp-kic-hex').value,
|
||||
kid: document.getElementById('sp-kid-hex').value,
|
||||
tar: document.getElementById('sp-tar').value,
|
||||
tar: (document.getElementById('sp-tar').value || '').replace(/[^0-9a-fA-F]/g, '').toUpperCase().padEnd(6, '0').slice(0, 6),
|
||||
cntr: document.getElementById('sp-cntr').value,
|
||||
kicKey: document.getElementById('sp-kic-key').value,
|
||||
kidKey: document.getElementById('sp-kid-key').value,
|
||||
@@ -8078,10 +8078,13 @@ async function pysimSendOta() {
|
||||
alert('Card reader server is not connected');
|
||||
return;
|
||||
}
|
||||
// Re-read the preset (counter and keys) before sending: the Cards tab may
|
||||
// have changed it after the form was filled, and a stale counter is
|
||||
// Re-read the preset (counter, keys, SPI) before sending: the Cards tab
|
||||
// may have changed it after the form was filled, and a stale counter is
|
||||
// rejected by the card (cntr_low) and then written back over the preset.
|
||||
spRefreshFromPreset('sp-card-sel');
|
||||
// The TAR is not re-applied: a packet may target a manually entered TAR
|
||||
// (push/link triggers, applet OTA) - the preset sets it only when a card
|
||||
// is picked or a chain is packed (v3.6.21).
|
||||
spRefreshFromPreset('sp-card-sel', true);
|
||||
const sp = document.getElementById('sp-result').value.replace(/[^0-9a-fA-F]/g, '').toUpperCase();
|
||||
if (!sp) { alert('No secured packet to send. Generate a secure packet first.'); return; }
|
||||
const sendResultEl = document.getElementById('sp-send-result');
|
||||
@@ -8476,7 +8479,7 @@ function getRamSpParams() {
|
||||
spi2: document.getElementById('sp-spi2-hex').value,
|
||||
kic: document.getElementById('sp-kic-hex').value,
|
||||
kid: document.getElementById('sp-kid-hex').value,
|
||||
tar: (document.getElementById('sp-tar').value || '000000').replace(/[^0-9a-fA-F]/g, ''),
|
||||
tar: (document.getElementById('sp-tar').value || '').replace(/[^0-9a-fA-F]/g, '').toUpperCase().padEnd(6, '0').slice(0, 6),
|
||||
cntr: (document.getElementById('sp-cntr').value || '0000000001').replace(/[^0-9a-fA-F]/g, ''),
|
||||
kicKey: (document.getElementById('sp-kic-key').value || '').replace(/[^0-9a-fA-F]/g, ''),
|
||||
kidKey: (document.getElementById('sp-kid-key').value || '').replace(/[^0-9a-fA-F]/g, ''),
|
||||
@@ -9762,8 +9765,11 @@ function cardsAutoSelectByIccid(iccid) {
|
||||
}
|
||||
|
||||
// Fill the SP form from the preset (no packet generation): the form is a
|
||||
// working copy, the preset stays the source of truth.
|
||||
function cardsApplyFields(idx) {
|
||||
// working copy, the preset stays the source of truth. `keepTar` leaves the
|
||||
// packet TAR alone: the plain SCP80 send re-reads only the preset-owned
|
||||
// counter/keys/SPI, a manually entered target TAR (push/link triggers,
|
||||
// applet OTA) must survive.
|
||||
function cardsApplyFields(idx, keepTar) {
|
||||
const c = cards[parseInt(idx)];
|
||||
if (!c) return false;
|
||||
document.getElementById('sp-spi1').value = c.spi1;
|
||||
@@ -9791,7 +9797,7 @@ function cardsApplyFields(idx) {
|
||||
document.getElementById('sp-kid-idx').value = ((kidByte >> 4) & 0x0F).toString(16).toUpperCase();
|
||||
document.getElementById('sp-kid-alg').value = (kidByte & 0x0F).toString(16).padStart(2, '0');
|
||||
updateSpKid();
|
||||
document.getElementById('sp-tar').value = cardsTarValue(_spTarKey, c[_spTarKey]);
|
||||
if (!keepTar) document.getElementById('sp-tar').value = cardsTarValue(_spTarKey, c[_spTarKey]);
|
||||
document.getElementById('sp-cntr').value = c.cntr;
|
||||
document.getElementById('sp-kic-key').value = c.kicKey;
|
||||
document.getElementById('sp-kid-key').value = c.kidKey;
|
||||
@@ -9800,8 +9806,8 @@ function cardsApplyFields(idx) {
|
||||
return true;
|
||||
}
|
||||
|
||||
function cardsApply(idx) {
|
||||
if (!cardsApplyFields(idx)) return;
|
||||
function cardsApply(idx, keepTar) {
|
||||
if (!cardsApplyFields(idx, keepTar)) return;
|
||||
genSp();
|
||||
}
|
||||
|
||||
@@ -9810,12 +9816,12 @@ function cardsApply(idx) {
|
||||
// filled, and a stale counter is rejected by the card (cntr_low) and would
|
||||
// then be written back over the preset. Returns the preset counter ('' when
|
||||
// no preset is selected).
|
||||
function spRefreshFromPreset(selId) {
|
||||
function spRefreshFromPreset(selId, keepTar) {
|
||||
const sel = document.getElementById(selId || 'sp-card-sel');
|
||||
const idx = sel ? parseInt(sel.value, 10) : NaN;
|
||||
if (isNaN(idx) || !cards[idx]) return '';
|
||||
if (selId === 'ram-card-sel') _spTarKey = 'tar';
|
||||
cardsApply(idx);
|
||||
cardsApply(idx, keepTar);
|
||||
return cards[idx].cntr;
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
const CACHE = 'simple-v293';
|
||||
const CACHE = 'simple-v294';
|
||||
const URLS = [
|
||||
'index.html',
|
||||
'help.html',
|
||||
|
||||
@@ -29,7 +29,8 @@ for (const fn of ['cardsTarValue', 'cardsApplyFields', 'cardsApply',
|
||||
eval(code);
|
||||
|
||||
// The SP form is a working copy: the preset is the source of truth, and
|
||||
// every SCP80/RAM operation re-reads it before starting (v3.6.3).
|
||||
// every SCP80/RAM operation re-reads it before starting (v3.6.3). The plain
|
||||
// SCP80 send keeps a hand-edited packet TAR (v3.6.21).
|
||||
function fakeEnv(selValue, selId, presetCntr) {
|
||||
const els = {};
|
||||
for (const id of ['sp-spi1', 'sp-spi2-hex', 'sp-kic-idx', 'sp-kic-alg',
|
||||
@@ -70,6 +71,17 @@ test('spRefreshFromPreset for the RAM selector targets the ISD TAR', () => {
|
||||
assert.strictEqual(env.els['sp-tar'].value, '000000');
|
||||
});
|
||||
|
||||
test('the pre-send refresh keeps a hand-edited packet TAR (v3.6.21)', () => {
|
||||
const env = fakeEnv('0', 'sp-card-sel', '0000000020');
|
||||
env.els['sp-tar'].value = 'AF4D01'; // e.g. a push/link trigger target
|
||||
assert.strictEqual(spRefreshFromPreset('sp-card-sel', true), '0000000020');
|
||||
assert.strictEqual(env.els['sp-tar'].value, 'AF4D01', 'the typed TAR must survive the send');
|
||||
assert.strictEqual(env.els['sp-cntr'].value, '0000000020', 'the counter still refreshes');
|
||||
// an explicit preset apply (or a pack) still sets the TAR
|
||||
cardsApplyFields(0);
|
||||
assert.strictEqual(env.els['sp-tar'].value, 'B00000');
|
||||
});
|
||||
|
||||
test('cardsApplyFields fills the form without generating a packet', () => {
|
||||
const env = fakeEnv('0', 'sp-card-sel', '0000000007');
|
||||
env.els['sp-cntr'].value = 'nonsense';
|
||||
|
||||
@@ -499,6 +499,11 @@ test('getRamSpParams reads the computed SPI2 byte, not the base select', () => {
|
||||
assert.ok(/spi2: document\.getElementById\('sp-spi2-hex'\)/.test(fn), fn);
|
||||
});
|
||||
|
||||
test('getRamSpParams normalises the packet TAR to three bytes', () => {
|
||||
const fn = extractFunc(html, 'getRamSpParams');
|
||||
assert.ok(/tar:[\s\S]*?padEnd\(6, '0'\)\.slice\(0, 6\),/.test(fn), fn);
|
||||
});
|
||||
|
||||
test('ramListingSpi2 requests the SMS-submit PoR for the listing queries', () => {
|
||||
// Apps (40) and ELF (20/10) listings can exceed the ENVELOPE response;
|
||||
// with SPI2=0x01 the card answers actual_response_sms_submit and the data
|
||||
|
||||
Reference in New Issue
Block a user