genSp: unciphered packets drop CPL prefix + prominent PoR status; v1.9.5
pySim encode_cmd transmits the 2-byte CPL only when ciphering is
applied; unciphered packets start at CHL. genSp always emitted it,
so every SPI1=0x00 packet diverged from the pySim reference at byte 0
(sp-verify MISMATCH) and carried a length octet pair real cards need
not expect.
- genSp output: bytesToHex(ciphering ? packet : packet.subarray(2));
MAC input unchanged (still covers the virtual-CPL frame, matching
pySim's sign-then-strip convention)
- Secured packet page: PoR verdict now rendered prominently in a
dedicated text-base semibold line ('PoR: por_ok' green / other
statuses red) above the small detail line; hidden when no PoR was
requested or the ENVELOPE failed
- sp.test.js: three unciphered expectations updated to CHL-first form;
ciphered vectors untouched (byte-identical)
- version 1.9.4 -> 1.9.5 everywhere; SW cache otaman-v15 -> otaman-v16
This commit is contained in:
+1
-1
@@ -54,7 +54,7 @@ Returns server version for compatibility checking.
|
|||||||
|
|
||||||
**Example response:**
|
**Example response:**
|
||||||
```json
|
```json
|
||||||
{"version": "1.9.4"}
|
{"version": "1.9.5"}
|
||||||
```
|
```
|
||||||
|
|
||||||
### `GET /api/status`
|
### `GET /api/status`
|
||||||
|
|||||||
+12
-3
@@ -18,7 +18,7 @@
|
|||||||
<div class="max-w-7xl mx-auto px-6 py-2">
|
<div class="max-w-7xl mx-auto px-6 py-2">
|
||||||
|
|
||||||
<div class="flex items-center justify-between mb-3">
|
<div class="flex items-center justify-between mb-3">
|
||||||
<h1 class="text-2xl font-bold text-heading">OTAMan <span id="slogan" class="text-sm font-normal text-gray-500 dark:text-slate-400 ml-2" data-l10n="SIM OTA with a Human Face">SIM OTA with a Human Face</span> <span class="text-xs text-gray-400 dark:text-slate-500 ml-1">v1.9.4</span></h1>
|
<h1 class="text-2xl font-bold text-heading">OTAMan <span id="slogan" class="text-sm font-normal text-gray-500 dark:text-slate-400 ml-2" data-l10n="SIM OTA with a Human Face">SIM OTA with a Human Face</span> <span class="text-xs text-gray-400 dark:text-slate-500 ml-1">v1.9.5</span></h1>
|
||||||
<div class="flex items-center gap-4">
|
<div class="flex items-center gap-4">
|
||||||
<button id="install-btn" class="px-2 py-1 text-xs rounded border border-gray-300 dark:border-slate-600 hover:bg-gray-200 dark:hover:bg-slate-700" style="display:none">INSTALL PWA [for offline use]</button>
|
<button id="install-btn" class="px-2 py-1 text-xs rounded border border-gray-300 dark:border-slate-600 hover:bg-gray-200 dark:hover:bg-slate-700" style="display:none">INSTALL PWA [for offline use]</button>
|
||||||
<a href="https://github.com/anttro/otaman" target="_blank" class="text-xs text-gray-400 hover:text-gray-600 dark:text-slate-500 dark:hover:text-slate-300">github</a>
|
<a href="https://github.com/anttro/otaman" target="_blank" class="text-xs text-gray-400 hover:text-gray-600 dark:text-slate-500 dark:hover:text-slate-300">github</a>
|
||||||
@@ -812,9 +812,10 @@
|
|||||||
<button onclick="genSp()" class="mb-3 px-5 py-2.5 bg-blue-600 text-white text-sm font-medium rounded hover:bg-blue-700" data-l10n="Generate Secure Packet">Generate Secure Packet</button>
|
<button onclick="genSp()" class="mb-3 px-5 py-2.5 bg-blue-600 text-white text-sm font-medium rounded hover:bg-blue-700" data-l10n="Generate Secure Packet">Generate Secure Packet</button>
|
||||||
<button onclick="pysimVerifySp()" id="sp-verify-btn" class="hidden mb-3 px-5 py-2.5 bg-slate-600 text-white text-sm font-medium rounded hover:bg-slate-700" data-l10n="Verify vs pySim">Verify vs pySim</button>
|
<button onclick="pysimVerifySp()" id="sp-verify-btn" class="hidden mb-3 px-5 py-2.5 bg-slate-600 text-white text-sm font-medium rounded hover:bg-slate-700" data-l10n="Verify vs pySim">Verify vs pySim</button>
|
||||||
<button onclick="pysimSendOta()" id="sp-send-btn" class="hidden mb-3 px-5 py-2.5 bg-emerald-600 text-white text-sm font-medium rounded hover:bg-emerald-700" data-l10n="Send to Card">Send to Card</button>
|
<button onclick="pysimSendOta()" id="sp-send-btn" class="hidden mb-3 px-5 py-2.5 bg-emerald-600 text-white text-sm font-medium rounded hover:bg-emerald-700" data-l10n="Send to Card">Send to Card</button>
|
||||||
|
<div id="sp-por-status" class="mt-2 text-base font-semibold font-mono hidden"></div>
|
||||||
<textarea id="sp-result" rows="3" readonly class="w-full font-mono border border-gray-300 dark:border-slate-600 text-sm rounded px-3 py-1.5 bg-gray-100 dark:bg-slate-800"></textarea>
|
<textarea id="sp-result" rows="3" readonly class="w-full font-mono border border-gray-300 dark:border-slate-600 text-sm rounded px-3 py-1.5 bg-gray-100 dark:bg-slate-800"></textarea>
|
||||||
<div id="sp-verify-result" class="mt-2 text-xs font-mono hidden"></div>
|
|
||||||
<div id="sp-send-result" class="mt-2 text-xs font-mono hidden"></div>
|
<div id="sp-send-result" class="mt-2 text-xs font-mono hidden"></div>
|
||||||
|
<div id="sp-verify-result" class="mt-2 text-xs font-mono hidden"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="tab-response" class="tab-content hidden">
|
<div id="tab-response" class="tab-content hidden">
|
||||||
@@ -2180,7 +2181,9 @@ function genSp() {
|
|||||||
packet.set(encrypted, 10);
|
packet.set(encrypted, 10);
|
||||||
}
|
}
|
||||||
|
|
||||||
resultEl.value = bytesToHex(packet);
|
// pySim encode_cmd parity: the 2-byte CPL prefix is transmitted only when
|
||||||
|
// ciphering is applied; unciphered packets start at CHL (v1.9.5).
|
||||||
|
resultEl.value = bytesToHex(ciphering ? packet : packet.subarray(2));
|
||||||
}
|
}
|
||||||
|
|
||||||
// ===== BER-TLV =====
|
// ===== BER-TLV =====
|
||||||
@@ -4036,7 +4039,9 @@ async function pysimSendOta() {
|
|||||||
if (!sp) { alert('No secured packet to send. Generate a secure packet first.'); return; }
|
if (!sp) { alert('No secured packet to send. Generate a secure packet first.'); return; }
|
||||||
const statusEl = document.getElementById('pysim-status');
|
const statusEl = document.getElementById('pysim-status');
|
||||||
const sendResultEl = document.getElementById('sp-send-result');
|
const sendResultEl = document.getElementById('sp-send-result');
|
||||||
|
const porStatusEl = document.getElementById('sp-por-status');
|
||||||
sendResultEl.classList.add('hidden');
|
sendResultEl.classList.add('hidden');
|
||||||
|
porStatusEl.classList.add('hidden');
|
||||||
statusEl.innerHTML = '<span class="text-gray-500">' + t('Sending OTA...') + '</span>';
|
statusEl.innerHTML = '<span class="text-gray-500">' + t('Sending OTA...') + '</span>';
|
||||||
try {
|
try {
|
||||||
const data = await pysimFetch('/api/send-ota', { sp, ...spParams() });
|
const data = await pysimFetch('/api/send-ota', { sp, ...spParams() });
|
||||||
@@ -4052,6 +4057,10 @@ async function pysimSendOta() {
|
|||||||
msg += ' | PoR status: ' + por.response_status + ' (TAR ' + por.tar + ')';
|
msg += ' | PoR status: ' + por.response_status + ' (TAR ' + por.tar + ')';
|
||||||
if (por.cntr) msg += ' | PoR CNTR: ' + por.cntr;
|
if (por.cntr) msg += ' | PoR CNTR: ' + por.cntr;
|
||||||
if (por.decoded && por.decoded.last_status_word) msg += ' | last SW ' + por.decoded.last_status_word + ' ' + (por.decoded.last_response_data || '');
|
if (por.decoded && por.decoded.last_status_word) msg += ' | last SW ' + por.decoded.last_status_word + ' ' + (por.decoded.last_response_data || '');
|
||||||
|
const okPor = por.response_status === 'por_ok';
|
||||||
|
porStatusEl.textContent = 'PoR: ' + por.response_status;
|
||||||
|
porStatusEl.classList.remove('hidden', okPor ? 'text-red-600' : 'text-green-600');
|
||||||
|
porStatusEl.classList.add(okPor ? 'text-green-600' : 'text-red-600');
|
||||||
}
|
}
|
||||||
sendResultEl.textContent = msg;
|
sendResultEl.textContent = msg;
|
||||||
if (por && por.raw) {
|
if (por && por.raw) {
|
||||||
|
|||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
const CACHE = 'otaman-v15';
|
const CACHE = 'otaman-v16';
|
||||||
const URLS = [
|
const URLS = [
|
||||||
'index.html',
|
'index.html',
|
||||||
'help.html',
|
'help.html',
|
||||||
|
|||||||
@@ -93,13 +93,13 @@ test('ciphered + CC SPI 16/01 (counter_must_be_higher, plaintext PoR)', () => {
|
|||||||
test('unciphered + CC SPI 02/09', () => {
|
test('unciphered + CC SPI 02/09', () => {
|
||||||
assert.strictEqual(
|
assert.strictEqual(
|
||||||
makeRun({ 'sp-spi1': '02', 'sp-spi2-hex': '09' }),
|
makeRun({ 'sp-spi1': '02', 'sp-spi2-hex': '09' }),
|
||||||
'001D1502091515B0000000000000010085A8CA1A9828B0BB00A40000023F00');
|
'1502091515B0000000000000010085A8CA1A9828B0BB00A40000023F00');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('unciphered packet uses CPL = octets from CHL to end (0x001d)', () => {
|
test('unciphered packet starts at CHL, no CPL prefix (pySim parity)', () => {
|
||||||
const out = makeRun({ 'sp-spi1': '02', 'sp-spi2-hex': '09' });
|
const out = makeRun({ 'sp-spi1': '02', 'sp-spi2-hex': '09' });
|
||||||
assert.strictEqual(out.slice(0, 4), '001D');
|
assert.strictEqual(out.slice(0, 2), '15');
|
||||||
assert.strictEqual(out.length, 62);
|
assert.strictEqual(out.length, 58);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('sysmocom public reference vector (spi1 04 / spi2 19, cntr=0)', () => {
|
test('sysmocom public reference vector (spi1 04 / spi2 19, cntr=0)', () => {
|
||||||
@@ -169,7 +169,7 @@ test('AES unciphered + CC SPI 12/09 (counter higher)', () => {
|
|||||||
'sp-kic-key': KIC_AES,
|
'sp-kic-key': KIC_AES,
|
||||||
'sp-kid-key': KID_AES,
|
'sp-kid-key': KID_AES,
|
||||||
}),
|
}),
|
||||||
'001D1512092222B0001100000000110029826122C7A0B79500A40004023F00');
|
'1512092222B0001100000000110029826122C7A0B79500A40004023F00');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('AES ciphered + CC SPI 1E/19 (counter +1)', () => {
|
test('AES ciphered + CC SPI 1E/19 (counter +1)', () => {
|
||||||
|
|||||||
+1
-1
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|||||||
|
|
||||||
[project]
|
[project]
|
||||||
name = "pysim-otaman-server"
|
name = "pysim-otaman-server"
|
||||||
version = "1.9.4"
|
version = "1.9.5"
|
||||||
description = "HTTP REST server wrapping pysim for the OTAMan PWA"
|
description = "HTTP REST server wrapping pysim for the OTAMan PWA"
|
||||||
requires-python = ">=3.8"
|
requires-python = ">=3.8"
|
||||||
# pysim is a git-only dependency installed explicitly by setup.bat/setup.sh.
|
# pysim is a git-only dependency installed explicitly by setup.bat/setup.sh.
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ from osmocom.construct import GsmOrUcs2Adapter
|
|||||||
from osmocom.tlv import BER_TLV_IE
|
from osmocom.tlv import BER_TLV_IE
|
||||||
|
|
||||||
|
|
||||||
VERSION = '1.9.4'
|
VERSION = '1.9.5'
|
||||||
|
|
||||||
|
|
||||||
# Static file serving (the PWA lives in <repo>/frontend, served by this server
|
# Static file serving (the PWA lives in <repo>/frontend, served by this server
|
||||||
|
|||||||
Reference in New Issue
Block a user