Files
simple/tests/test_adm_verify.py
T
catarrh 2c793720f6 ui: verify the ADM PIN from the matched card preset (v2.7.8)
The preset ADM key was stored but never used: the header badge showed
whether a key exists and whether the card was verified, yet the only way
to verify was the pySim command line.

- POST /api/verify-adm builds the TS 102 221 VERIFY itself (CHV number
  from the card model, short keys padded to 8 bytes with 'f') so the raw
  SW is reported: 63Cx -> attempts_left, 6983/9804 -> blocked, 6982 ->
  security error.  The key is never stored and is redacted from request
  logs.
- PWA: the header ADM badge is clickable when the matched preset has a
  key; a failed file-manager read/write (6982/9804) shows a Verify ADM
  button next to the error.  Every retry after a failure asks for
  confirmation and shows the remaining attempts (stronger text on the
  last attempt); a blocked ADM disables both entry points until the card
  session changes.  No automatic retries.
- tests: tests/test_adm_verify.py (fake scc, APDU/SW mapping, redaction)
  and frontend/tests/adm_verify.test.js (retry prompt, SW classifier,
  wiring) + card_state indicator expectations
- docs/api.md, help EN/RU, AGENTS; version trio 2.7.8; sw cache v211
2026-09-20 22:09:39 +03:00

83 lines
3.0 KiB
Python

# coding=utf-8
"""Tests for the ADM verification helper used by ``POST /api/verify-adm``.
The helper builds the TS 102 221 VERIFY APDU itself (instead of pySim's
``verify_chv``) so the raw SW can be reported back: the UI warns about the
remaining attempts after a 63Cx and stops trying once the ADM is blocked.
"""
import unittest
from types import SimpleNamespace
from pysim_simple_server import server
class FakeScc:
"""Records the VERIFY APDU and returns a canned SW."""
cla_byte = '00'
def __init__(self, sw='9000'):
self.sw = sw
self.apdus = []
def send_apdu(self, pdu):
self.apdus.append(pdu)
return '', self.sw
def _app(chv=0x0A):
return SimpleNamespace(card=SimpleNamespace(_adm_chv_num=chv),
rs=SimpleNamespace(adm_verified=False))
class AdmVerifyTests(unittest.TestCase):
def test_success_sets_adm_verified(self):
app = _app()
scc = FakeScc('9000')
res = server._verify_adm(scc, app, '0011')
self.assertEqual(res, {'ok': True, 'sw': '9000'})
self.assertTrue(app.rs.adm_verified)
# short keys are padded to the 8 CHV bytes with 'f' (pySim behaviour)
self.assertEqual(scc.apdus, ['0020000A08' + '0011' + 'f' * 12])
def test_full_length_key_is_not_padded(self):
scc = FakeScc('9000')
server._verify_adm(scc, _app(), 'DEADBEEFDEADBEEF')
self.assertEqual(scc.apdus, ['0020000A08' + 'deadbeefdeadbeef'])
def test_chv_number_comes_from_the_card_model(self):
scc = FakeScc('9000')
server._verify_adm(scc, _app(chv=0x0B), '0011')
self.assertIn('0020000B', scc.apdus[0])
def test_63cx_reports_attempts_left(self):
app = _app()
res = server._verify_adm(FakeScc('63C2'), app, '0011')
self.assertEqual(res, {'ok': False, 'sw': '63C2', 'attempts_left': 2})
self.assertFalse(app.rs.adm_verified)
def test_last_attempt_reports_zero(self):
res = server._verify_adm(FakeScc('63C0'), _app(), '0011')
self.assertEqual(res, {'ok': False, 'sw': '63C0', 'attempts_left': 0})
def test_blocked_sw_marks_blocked(self):
for sw in ('6983', '9804'):
res = server._verify_adm(FakeScc(sw), _app(), '0011')
self.assertEqual(res, {'ok': False, 'sw': sw, 'blocked': True})
def test_other_sw_is_a_plain_error(self):
self.assertEqual(server._verify_adm(FakeScc('6982'), _app(), '0011'),
{'ok': False, 'sw': '6982', 'error': 'Security status not satisfied'})
self.assertEqual(server._verify_adm(FakeScc('6A88'), _app(), '0011'),
{'ok': False, 'sw': '6A88', 'error': 'Error'})
def test_redaction_masks_adm(self):
out = server._redact_psk_fields({'adm': '0011', 'psk_hex': 'AA', 'other': 'x'})
self.assertEqual(out, {'adm': '<redacted>', 'psk_hex': '<redacted>', 'other': 'x'})
self.assertEqual(server._redact_psk_fields({'adm': ''}), {'adm': ''})
if __name__ == '__main__':
unittest.main()