v3.29.1: the per-row keysets in the outfile preview
The import applied one global default keyset to every TAR entry of every imported card. The preview's TAR table (TAR + MSL, the three role rows + "+ TAR") now has a keyset select per row, mirroring the preset editor: an empty "— none —" plus the keysets found in the file with their KIc/KID, the first preselected, and the editor's amber warning when a non-zero MSL has no keyset. Each TAR's packets then use exactly the chosen keyset. - PWA: the global "Default keyset for the TARs" select (and the request's tar_keyset) is gone; cardsOutfileTarsFromDom/FromForm carry each row's keyset; cardsOutfileKeysets() became cardsOutfileKeysetOptions() (the union with the KIc/KID bytes for the labels). - Server: a `tars` entry's own `keyset` (1-15; an unusable value is a 400) wins, then the optional global `tar_keyset` (the API shorthand), then the card's first keyset - parse_outfile no longer clobbers per-entry keysets with the card-level fallback. presets.normalise_keyset_ref() is module-level (one rule for the store and the parser). - Docs: help EN/RU, api.md (the tars entry keyset + the global fallback), the repo AGENTS. Tests: the per-entry/global/card fallbacks, the bad-keyset 400, the options helper, the row select and round-trip, the import body. 925 frontend + 944 Python; sw cache simple-v448.
This commit is contained in:
@@ -150,10 +150,18 @@ def normalise_tars(tars, tar_keyset=None):
|
||||
raise ValueError('TAR must be three hex bytes: %r' % tar)
|
||||
if not re.fullmatch(r'[0-9A-F]{2}', msl):
|
||||
raise ValueError('MSL must be one hex byte: %r' % msl)
|
||||
ks_raw = t.get('keyset')
|
||||
if ks_raw in (None, ''):
|
||||
keyset = None
|
||||
else:
|
||||
keyset = presets.normalise_keyset_ref(ks_raw)
|
||||
if keyset is None:
|
||||
raise ValueError('keyset must be 1-%02X: %r'
|
||||
% (presets.KVN_MAX, ks_raw))
|
||||
out.append({'role': role if role in presets.TAR_ROLES else '',
|
||||
'tar': tar, 'msl': msl,
|
||||
'desc': str(t.get('desc') or '').strip(),
|
||||
'keyset': tar_keyset or None})
|
||||
'keyset': keyset or (tar_keyset or None)})
|
||||
seen = set()
|
||||
for t in out:
|
||||
if t['tar'] in seen:
|
||||
@@ -248,8 +256,11 @@ def parse_outfile(text, name='', algorithm16='3des2', tars=None, tar_keyset=None
|
||||
# Every TAR gets the caller's global default keyset; without one the
|
||||
# card's first keyset is used (a multi-keyset card would otherwise
|
||||
# fail the store's "a non-zero MSL needs a default keyset" rule).
|
||||
# A per-entry keyset (chosen in the preview) wins; the caller's global
|
||||
# default - else the card's first keyset - fills the rest.
|
||||
card_keyset = tar_keyset or presets.keyset_kvn(keysets[0])
|
||||
card_tars = [dict(t, keyset=card_keyset) for t in tars_base]
|
||||
card_tars = [dict(t, keyset=t.get('keyset') or card_keyset)
|
||||
for t in tars_base]
|
||||
entry = {'name': str(name or '').strip() or iccid, 'iccid': iccid,
|
||||
'keysets': keysets, 'tars': card_tars}
|
||||
for field, col in (('pin1', 'PIN1'), ('pin2', 'PIN2'),
|
||||
|
||||
@@ -218,6 +218,25 @@ def tar_keyset(preset, tar_hex):
|
||||
return (tar_entry(preset, tar_hex) or {}).get('keyset') or None
|
||||
|
||||
|
||||
def normalise_keyset_ref(v):
|
||||
"""A TAR's default keyset number (int 1-15) or None. Accepts an int and
|
||||
the decimal ('1'..'15') or hex-ish ('01'..'0F') string forms; an unusable
|
||||
value becomes None - the validation reports a missing keyset where one is
|
||||
mandatory. Shared by the store and the outfile import (v3.29.1)."""
|
||||
if v is None or v == '':
|
||||
return None
|
||||
s = str(v).strip().upper()
|
||||
if re.fullmatch(r'[0-9]{1,2}', s):
|
||||
n = int(s, 10)
|
||||
if 1 <= n <= KVN_MAX:
|
||||
return n
|
||||
if re.fullmatch(r'[0-9A-F]{1,2}', s):
|
||||
n = int(s, 16)
|
||||
if 1 <= n <= KVN_MAX:
|
||||
return n
|
||||
return None
|
||||
|
||||
|
||||
class PresetError(Exception):
|
||||
"""A refused mutation (validation, duplicate ICCID, ...)."""
|
||||
|
||||
@@ -360,7 +379,7 @@ class PresetStore:
|
||||
'tar': re.sub(r'\s', '', str(t.get('tar') or '')).upper(),
|
||||
'msl': re.sub(r'\s', '', str(t.get('msl') or '')).upper(),
|
||||
'desc': str(t.get('desc') or '').strip(),
|
||||
'keyset': self._normalise_keyset_ref(t.get('keyset')),
|
||||
'keyset': normalise_keyset_ref(t.get('keyset')),
|
||||
})
|
||||
else:
|
||||
msl = re.sub(r'\s', '', str(fields.get('spi1') or '')).upper()
|
||||
@@ -392,25 +411,6 @@ class PresetStore:
|
||||
'keyset': e.get('keyset')})
|
||||
return out
|
||||
|
||||
@staticmethod
|
||||
def _normalise_keyset_ref(v):
|
||||
"""A TAR's default keyset number (int 1-15) or None. Accepts an int
|
||||
and the decimal ('1'..'15') or hex-ish ('01'..'0F') string forms; an
|
||||
unusable value becomes None - the validation reports a missing keyset
|
||||
where one is mandatory (v3.26.0)."""
|
||||
if v is None or v == '':
|
||||
return None
|
||||
s = str(v).strip().upper()
|
||||
if re.fullmatch(r'[0-9]{1,2}', s):
|
||||
n = int(s, 10)
|
||||
if 1 <= n <= KVN_MAX:
|
||||
return n
|
||||
if re.fullmatch(r'[0-9A-F]{1,2}', s):
|
||||
n = int(s, 16)
|
||||
if 1 <= n <= KVN_MAX:
|
||||
return n
|
||||
return None
|
||||
|
||||
def _normalise_keyset(self, ks):
|
||||
"""One keyset, cleaned. The counter is kept as typed here and
|
||||
validated + padded in _validate_keyset() - a bad value must be
|
||||
|
||||
@@ -37,7 +37,7 @@ from osmocom.tlv import BER_TLV_IE
|
||||
from osmocom.utils import rpad
|
||||
|
||||
|
||||
VERSION = '3.29.0'
|
||||
VERSION = '3.29.1'
|
||||
|
||||
MAX_ENVELOPE_SEGMENTS = 5 # max SMS segments for outgoing C-APDU in ENVELOPE
|
||||
|
||||
|
||||
Reference in New Issue
Block a user