Deepen AUTHENTICATE decode: P2 auth context + AUTN/AUTS decomposition
- Add a P2 spec for AUTHENTICATE (auth context bits + specific-reference-data flag), so P2 is displayed instead of only leaking through cmd.context. - Fix P1 name to 'No indication' (was the misleading 'Run GSM algorithm'). - Decompose AUTN into SQN(xor)AK / AMF / MAC and AUTS into SQN(xor)AK / MAC-S per TS 33.102; surface 'specific reference data' key flag.
This commit is contained in:
1 parent
35067f8d9d
commit
0fdaed503e
3 files changed
+66
-22
No files matched your search
+14
-5
@@ -454,7 +454,7 @@ function cmd_description(m) {
|
|||||||
function sw_text(m) {
|
function sw_text(m) {
|
||||||
const d = m.decoded;
|
const d = m.decoded;
|
||||||
if (!d || !d.sw) return '';
|
if (!d || !d.sw) return '';
|
||||||
return d.sw.sw1 + ' ' + d.sw.sw2;
|
return (d.sw.sw1 + ' ' + d.sw.sw2).toUpperCase();
|
||||||
}
|
}
|
||||||
|
|
||||||
function sw_class(m) {
|
function sw_class(m) {
|
||||||
@@ -536,7 +536,11 @@ function responseRows(resp) {
|
|||||||
if (resp.ck) rows.push(['CK', resp.ck]);
|
if (resp.ck) rows.push(['CK', resp.ck]);
|
||||||
if (resp.ik) rows.push(['IK', resp.ik]);
|
if (resp.ik) rows.push(['IK', resp.ik]);
|
||||||
if (resp.kc) rows.push(['Kc', resp.kc]);
|
if (resp.kc) rows.push(['Kc', resp.kc]);
|
||||||
if (resp.auts) rows.push(['AUTS', resp.auts]);
|
if (resp.auts) {
|
||||||
|
rows.push(['AUTS', resp.auts]);
|
||||||
|
if (resp.sqn_ak) rows.push(['SQN⊕AK', resp.sqn_ak + ' (concealed)']);
|
||||||
|
if (resp.mac_s) rows.push(['MAC-S', resp.mac_s]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (resp.code !== undefined) {
|
if (resp.code !== undefined) {
|
||||||
@@ -564,10 +568,15 @@ function cmdRows(cmd) {
|
|||||||
const rows = [];
|
const rows = [];
|
||||||
|
|
||||||
// AUTHENTICATE command body
|
// AUTHENTICATE command body
|
||||||
if (cmd.context !== undefined) {
|
if (cmd.rand !== undefined || cmd.autn !== undefined || cmd.specific_key) {
|
||||||
rows.push(['Auth context', cmd.context]);
|
if (cmd.specific_key) rows.push(['Key', 'specific reference data']);
|
||||||
if (cmd.rand) rows.push(['RAND', cmd.rand]);
|
if (cmd.rand) rows.push(['RAND', cmd.rand]);
|
||||||
if (cmd.autn) rows.push(['AUTN', cmd.autn]);
|
if (cmd.autn) {
|
||||||
|
rows.push(['AUTN', cmd.autn]);
|
||||||
|
if (cmd.sqn_ak) rows.push(['SQN⊕AK', cmd.sqn_ak + ' (concealed)']);
|
||||||
|
if (cmd.amf) rows.push(['AMF', cmd.amf]);
|
||||||
|
if (cmd.mac) rows.push(['MAC', cmd.mac]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ENVELOPE fields
|
// ENVELOPE fields
|
||||||
|
|||||||
@@ -552,7 +552,14 @@ APDU_SPEC = {
|
|||||||
},
|
},
|
||||||
0x88: {
|
0x88: {
|
||||||
'name': 'AUTHENTICATE',
|
'name': 'AUTHENTICATE',
|
||||||
'p1': {0x00: 'Run GSM algorithm', 0x80: 'Run 3G algo (resynchronisation)'},
|
'p1': {0x00: 'No indication'},
|
||||||
|
'p2': {'label': 'Auth context', 'bits': {
|
||||||
|
0x00: 'GSM context',
|
||||||
|
0x01: '3G/EPS/5G context',
|
||||||
|
0x02: 'VGCS/VBS context',
|
||||||
|
0x04: 'GBA context',
|
||||||
|
0x80: 'Specific reference data',
|
||||||
|
}},
|
||||||
'body': {'label': 'Challenge/session key'},
|
'body': {'label': 'Challenge/session key'},
|
||||||
},
|
},
|
||||||
0x89: {
|
0x89: {
|
||||||
@@ -1127,9 +1134,15 @@ def _decode_auth_3g(data):
|
|||||||
if tag == 0xDC: # synchronisation failure → length byte + AUTS
|
if tag == 0xDC: # synchronisation failure → length byte + AUTS
|
||||||
if len(data) >= 2:
|
if len(data) >= 2:
|
||||||
auts_len = data[1]
|
auts_len = data[1]
|
||||||
return {'type': '3G', 'status': 'sync fail',
|
auts = data[2:2 + auts_len]
|
||||||
'auts': data[2:2 + auts_len].hex().upper()}
|
else:
|
||||||
return {'type': '3G', 'status': 'sync fail', 'auts': data[1:].hex().upper()}
|
auts = data[1:]
|
||||||
|
result = {'type': '3G', 'status': 'sync fail', 'auts': auts.hex().upper()}
|
||||||
|
# AUTS = SQN_MS⊕AK (6) || MAC-S (8) (TS 33.102)
|
||||||
|
if len(auts) >= 14:
|
||||||
|
result['sqn_ak'] = auts[0:6].hex().upper()
|
||||||
|
result['mac_s'] = auts[6:14].hex().upper()
|
||||||
|
return result
|
||||||
# tag 0xDB: success → length-prefixed RES, CK, IK, (KC)
|
# tag 0xDB: success → length-prefixed RES, CK, IK, (KC)
|
||||||
result = {'type': '3G', 'status': 'success'}
|
result = {'type': '3G', 'status': 'success'}
|
||||||
i = 1
|
i = 1
|
||||||
@@ -1170,18 +1183,12 @@ def _decode_response_for(ins, data):
|
|||||||
|
|
||||||
# ──────────────────── Command-body decoders ────────────────────
|
# ──────────────────── Command-body decoders ────────────────────
|
||||||
|
|
||||||
AUTH_CONTEXTS = {
|
|
||||||
0: 'GSM',
|
|
||||||
1: '3G (UMTS)',
|
|
||||||
2: 'VGC/VBS',
|
|
||||||
4: 'GBA',
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
def _decode_auth_cmd(data, p2):
|
def _decode_auth_cmd(data, p2):
|
||||||
"""Decode an AUTHENTICATE command body (RAND / RAND+AUTN)."""
|
"""Decode an AUTHENTICATE command body (RAND / RAND+AUTN)."""
|
||||||
ctx = p2 & 0x07
|
ctx = p2 & 0x07
|
||||||
result = {'context': AUTH_CONTEXTS.get(ctx, f'unknown ({ctx})')}
|
result = {}
|
||||||
|
if p2 & 0x80:
|
||||||
|
result['specific_key'] = True
|
||||||
if ctx in (0, 1) and data:
|
if ctx in (0, 1) and data:
|
||||||
i = 0
|
i = 0
|
||||||
if i < len(data):
|
if i < len(data):
|
||||||
@@ -1189,10 +1196,16 @@ def _decode_auth_cmd(data, p2):
|
|||||||
i += 1
|
i += 1
|
||||||
result['rand'] = data[i:i + rlen].hex().upper()
|
result['rand'] = data[i:i + rlen].hex().upper()
|
||||||
i += rlen
|
i += rlen
|
||||||
if ctx == 1 and i < len(data): # 3G → AUTN
|
if ctx == 1 and i < len(data): # 3G/EPS/5G → AUTN
|
||||||
alen = data[i]
|
alen = data[i]
|
||||||
i += 1
|
i += 1
|
||||||
result['autn'] = data[i:i + alen].hex().upper()
|
autn = data[i:i + alen]
|
||||||
|
result['autn'] = autn.hex().upper()
|
||||||
|
# AUTN = SQN⊕AK (6) || AMF (2) || MAC-A (8) (TS 33.102)
|
||||||
|
if len(autn) >= 16:
|
||||||
|
result['sqn_ak'] = autn[0:6].hex().upper()
|
||||||
|
result['amf'] = autn[6:8].hex().upper()
|
||||||
|
result['mac'] = autn[8:16].hex().upper()
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
+24
-2
@@ -361,17 +361,39 @@ class TestAuthCommand(unittest.TestCase):
|
|||||||
r = decode_message(bytes.fromhex(
|
r = decode_message(bytes.fromhex(
|
||||||
'0088008122104A75BA425D438C549EF35BA5E3DD53051065E04D80E4DC8000A5C7FD842F41395E6135'))
|
'0088008122104A75BA425D438C549EF35BA5E3DD53051065E04D80E4DC8000A5C7FD842F41395E6135'))
|
||||||
cmd = r['cmd']
|
cmd = r['cmd']
|
||||||
self.assertEqual(cmd['context'], '3G (UMTS)')
|
self.assertNotIn('context', cmd)
|
||||||
|
self.assertEqual(cmd['specific_key'], True)
|
||||||
self.assertEqual(cmd['rand'], '4A75BA425D438C549EF35BA5E3DD5305')
|
self.assertEqual(cmd['rand'], '4A75BA425D438C549EF35BA5E3DD5305')
|
||||||
self.assertEqual(cmd['autn'], '65E04D80E4DC8000A5C7FD842F41395E')
|
self.assertEqual(cmd['autn'], '65E04D80E4DC8000A5C7FD842F41395E')
|
||||||
|
self.assertEqual(cmd['sqn_ak'], '65E04D80E4DC')
|
||||||
|
self.assertEqual(cmd['amf'], '8000')
|
||||||
|
self.assertEqual(cmd['mac'], 'A5C7FD842F41395E')
|
||||||
|
|
||||||
|
def test_auth_p2_context(self):
|
||||||
|
r = decode_message(bytes.fromhex(
|
||||||
|
'0088008122104A75BA425D438C549EF35BA5E3DD53051065E04D80E4DC8000A5C7FD842F41395E6135'))
|
||||||
|
p2 = r['p2']
|
||||||
|
self.assertEqual(p2['raw'], '81')
|
||||||
|
self.assertIn('3G/EPS/5G context', p2['bits'])
|
||||||
|
self.assertIn('Specific reference data', p2['bits'])
|
||||||
|
|
||||||
def test_gsm_rand_only(self):
|
def test_gsm_rand_only(self):
|
||||||
from simtrace2_pysniff.server.decode import _decode_auth_cmd
|
from simtrace2_pysniff.server.decode import _decode_auth_cmd
|
||||||
r = _decode_auth_cmd(bytes.fromhex('10' + '00' * 16), 0x00)
|
r = _decode_auth_cmd(bytes.fromhex('10' + '00' * 16), 0x00)
|
||||||
self.assertEqual(r['context'], 'GSM')
|
self.assertNotIn('context', r)
|
||||||
|
self.assertNotIn('specific_key', r)
|
||||||
self.assertEqual(r['rand'], '00' * 16)
|
self.assertEqual(r['rand'], '00' * 16)
|
||||||
self.assertNotIn('autn', r)
|
self.assertNotIn('autn', r)
|
||||||
|
|
||||||
|
def test_sync_fail_auts(self):
|
||||||
|
from simtrace2_pysniff.server.decode import _decode_auth
|
||||||
|
auts = bytes.fromhex('010203040506' + '0a0b0c0d0e0f1011')
|
||||||
|
r = _decode_auth(b'\xdc' + bytes([len(auts)]) + auts)
|
||||||
|
self.assertEqual(r['status'], 'sync fail')
|
||||||
|
self.assertEqual(r['auts'], auts.hex().upper())
|
||||||
|
self.assertEqual(r['sqn_ak'], '010203040506')
|
||||||
|
self.assertEqual(r['mac_s'], '0A0B0C0D0E0F1011')
|
||||||
|
|
||||||
|
|
||||||
class TestEventDownload(unittest.TestCase):
|
class TestEventDownload(unittest.TestCase):
|
||||||
def test_location_status(self):
|
def test_location_status(self):
|
||||||
|
|||||||
Reference in new issue
Block a user