mirror of
https://gitea.osmocom.org/sim-card/pysim.git
synced 2026-03-26 23:38:32 +03:00
pysim-Shell: Add sysmocom SJA2 card specific bits
Depending on the ATR, we register the various sysmocom SJA2 card model specific files [or not]. Change-Id: Id410489841bb9020ddbf74de9114d808b1d5adb6
This commit is contained in:
@@ -42,12 +42,14 @@ from pySim.utils import h2b, swap_nibbles, rpad, b2h, h2s, JsonEncoder, bertlv_p
|
|||||||
from pySim.utils import dec_st, sanitize_pin_adm, tabulate_str_list, is_hex, boxed_heading_str
|
from pySim.utils import dec_st, sanitize_pin_adm, tabulate_str_list, is_hex, boxed_heading_str
|
||||||
from pySim.card_handler import CardHandler
|
from pySim.card_handler import CardHandler
|
||||||
|
|
||||||
from pySim.filesystem import CardMF, RuntimeState, CardDF, CardADF
|
from pySim.filesystem import CardMF, RuntimeState, CardDF, CardADF, CardModel
|
||||||
from pySim.ts_51_011 import CardProfileSIM, DF_TELECOM, DF_GSM
|
from pySim.ts_51_011 import CardProfileSIM, DF_TELECOM, DF_GSM
|
||||||
from pySim.ts_102_221 import CardProfileUICC
|
from pySim.ts_102_221 import CardProfileUICC
|
||||||
from pySim.ts_31_102 import CardApplicationUSIM
|
from pySim.ts_31_102 import CardApplicationUSIM
|
||||||
from pySim.ts_31_103 import CardApplicationISIM
|
from pySim.ts_31_103 import CardApplicationISIM
|
||||||
|
|
||||||
|
import pySim.sysmocom_sja2
|
||||||
|
|
||||||
from pySim.card_key_provider import CardKeyProviderCsv, card_key_provider_register, card_key_provider_get_field
|
from pySim.card_key_provider import CardKeyProviderCsv, card_key_provider_register, card_key_provider_get_field
|
||||||
|
|
||||||
def init_card(sl):
|
def init_card(sl):
|
||||||
@@ -84,6 +86,8 @@ def init_card(sl):
|
|||||||
rs.mf.add_file(DF_TELECOM())
|
rs.mf.add_file(DF_TELECOM())
|
||||||
rs.mf.add_file(DF_GSM())
|
rs.mf.add_file(DF_GSM())
|
||||||
|
|
||||||
|
CardModel.apply_matching_models(scc, rs)
|
||||||
|
|
||||||
# inform the transport that we can do context-specific SW interpretation
|
# inform the transport that we can do context-specific SW interpretation
|
||||||
sl.set_sw_interpreter(rs)
|
sl.set_sw_interpreter(rs)
|
||||||
|
|
||||||
|
|||||||
@@ -27,6 +27,8 @@ not the actual contents / runtime state of interacting with a given smart card.
|
|||||||
import code
|
import code
|
||||||
import tempfile
|
import tempfile
|
||||||
import json
|
import json
|
||||||
|
import abc
|
||||||
|
import inspect
|
||||||
|
|
||||||
import cmd2
|
import cmd2
|
||||||
from cmd2 import CommandSet, with_default_category, with_argparser
|
from cmd2 import CommandSet, with_default_category, with_argparser
|
||||||
@@ -34,10 +36,13 @@ import argparse
|
|||||||
|
|
||||||
from typing import cast, Optional, Iterable, List, Any, Dict, Tuple
|
from typing import cast, Optional, Iterable, List, Any, Dict, Tuple
|
||||||
|
|
||||||
|
from smartcard.util import toBytes
|
||||||
|
|
||||||
from pySim.utils import sw_match, h2b, b2h, i2h, is_hex, auto_int, bertlv_parse_one, Hexstr
|
from pySim.utils import sw_match, h2b, b2h, i2h, is_hex, auto_int, bertlv_parse_one, Hexstr
|
||||||
from pySim.construct import filter_dict, parse_construct
|
from pySim.construct import filter_dict, parse_construct
|
||||||
from pySim.exceptions import *
|
from pySim.exceptions import *
|
||||||
from pySim.jsonpath import js_path_find, js_path_modify
|
from pySim.jsonpath import js_path_find, js_path_modify
|
||||||
|
from pySim.commands import SimCardCommands
|
||||||
|
|
||||||
class CardFile(object):
|
class CardFile(object):
|
||||||
"""Base class for all objects in the smart card filesystem.
|
"""Base class for all objects in the smart card filesystem.
|
||||||
@@ -1427,3 +1432,30 @@ class CardProfile(object):
|
|||||||
Tuple of two strings
|
Tuple of two strings
|
||||||
"""
|
"""
|
||||||
return interpret_sw(self.sw, sw)
|
return interpret_sw(self.sw, sw)
|
||||||
|
|
||||||
|
|
||||||
|
class CardModel(abc.ABC):
|
||||||
|
"""A specific card model, typically having some additional vendor-specific files"""
|
||||||
|
_atrs = []
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
@abc.abstractmethod
|
||||||
|
def add_files(cls, rs:RuntimeState):
|
||||||
|
"""Add model specific files to given RuntimeState."""
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def match(cls, scc:SimCardCommands) -> bool:
|
||||||
|
"""Test if given card matches this model."""
|
||||||
|
card_atr = scc.get_atr()
|
||||||
|
for atr in cls._atrs:
|
||||||
|
atr_bin = toBytes(atr)
|
||||||
|
if atr_bin == card_atr:
|
||||||
|
print("Detected CardModel:", cls.__name__)
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def apply_matching_models(scc:SimCardCommands, rs:RuntimeState):
|
||||||
|
for m in CardModel.__subclasses__():
|
||||||
|
if m.match(scc):
|
||||||
|
m.add_files(rs)
|
||||||
|
|||||||
241
pySim/sysmocom_sja2.py
Normal file
241
pySim/sysmocom_sja2.py
Normal file
@@ -0,0 +1,241 @@
|
|||||||
|
# coding=utf-8
|
||||||
|
"""Utilities / Functions related to sysmocom SJA2 cards
|
||||||
|
|
||||||
|
(C) 2021 by Harald Welte <laforge@osmocom.org>
|
||||||
|
|
||||||
|
This program is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation, either version 2 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from pytlv.TLV import *
|
||||||
|
from struct import pack, unpack
|
||||||
|
from pySim.utils import *
|
||||||
|
from pySim.filesystem import *
|
||||||
|
from pySim.ts_102_221 import CardProfileUICC
|
||||||
|
from pySim.construct import *
|
||||||
|
from construct import *
|
||||||
|
import pySim
|
||||||
|
|
||||||
|
key_type2str = {
|
||||||
|
0: 'kic',
|
||||||
|
1: 'kid',
|
||||||
|
2: 'kik',
|
||||||
|
3: 'any',
|
||||||
|
}
|
||||||
|
|
||||||
|
key_algo2str = {
|
||||||
|
0: 'des',
|
||||||
|
1: 'aes'
|
||||||
|
}
|
||||||
|
|
||||||
|
mac_length = {
|
||||||
|
0: 8,
|
||||||
|
1: 4
|
||||||
|
}
|
||||||
|
|
||||||
|
class EF_PIN(TransparentEF):
|
||||||
|
def __init__(self, fid, name):
|
||||||
|
super().__init__(fid, name=name, desc='%s PIN file' % name)
|
||||||
|
def _decode_bin(self, raw_bin_data):
|
||||||
|
u = unpack('!BBB8s', raw_bin_data[:11])
|
||||||
|
res = {'enabled': (True, False)[u[0] & 0x01],
|
||||||
|
'initialized': (True, False)[u[0] & 0x02],
|
||||||
|
'disable_able': (False, True)[u[0] & 0x10],
|
||||||
|
'unblock_able': (False, True)[u[0] & 0x20],
|
||||||
|
'change_able': (False, True)[u[0] & 0x40],
|
||||||
|
'valid': (False, True)[u[0] & 0x80],
|
||||||
|
'attempts_remaining': u[1],
|
||||||
|
'maximum_attempts': u[2],
|
||||||
|
'pin': u[3].hex(),
|
||||||
|
}
|
||||||
|
if len(raw_bin_data) == 21:
|
||||||
|
u2 = unpack('!BB8s', raw_bin_data[11:10])
|
||||||
|
res['attempts_remaining_puk'] = u2[0]
|
||||||
|
res['maximum_attempts_puk'] = u2[1]
|
||||||
|
res['puk'] = u2[2].hex()
|
||||||
|
return res
|
||||||
|
|
||||||
|
class EF_MILENAGE_CFG(TransparentEF):
|
||||||
|
def __init__(self, fid='6f21', name='EF.MILENAGE_CFG', desc='Milenage connfiguration'):
|
||||||
|
super().__init__(fid, name=name, desc=desc)
|
||||||
|
def _decode_bin(self, raw_bin_data):
|
||||||
|
u = unpack('!BBBBB16s16s16s16s16s', raw_bin_data)
|
||||||
|
return {'r1': u[0], 'r2': u[1], 'r3': u[2], 'r4': u[3], 'r5': u[4],
|
||||||
|
'c1': u[5].hex(),
|
||||||
|
'c2': u[6].hex(),
|
||||||
|
'c3': u[7].hex(),
|
||||||
|
'c4': u[8].hex(),
|
||||||
|
'c5': u[9].hex(),
|
||||||
|
}
|
||||||
|
|
||||||
|
class EF_0348_KEY(LinFixedEF):
|
||||||
|
def __init__(self, fid='6f22', name='EF.0348_KEY', desc='TS 03.48 OTA Keys'):
|
||||||
|
super().__init__(fid, name=name, desc=desc, rec_len={27,35})
|
||||||
|
def _decode_record_bin(self, raw_bin_data):
|
||||||
|
u = unpack('!BBB', raw_bin_data[0:3])
|
||||||
|
key_algo = (u[2] >> 6) & 1
|
||||||
|
key_length = ((u[2] >> 3) & 3) * 8
|
||||||
|
return {'sec_domain': u[0],
|
||||||
|
'key_set_version': u[1],
|
||||||
|
'key_type': key_type2str[u[2] & 3],
|
||||||
|
'key_length': key_length,
|
||||||
|
'algorithm': key_algo2str[key_algo],
|
||||||
|
'mac_length': mac_length[(u[2] >> 7)],
|
||||||
|
'key': raw_bin_data[3:key_length].hex()
|
||||||
|
}
|
||||||
|
|
||||||
|
class EF_0348_COUNT(LinFixedEF):
|
||||||
|
def __init__(self, fid='6f23', name='EF.0348_COUNT', desc='TS 03.48 OTA Counters'):
|
||||||
|
super().__init__(fid, name=name, desc=desc, rec_len={7,7})
|
||||||
|
def _decode_record_bin(self, raw_bin_data):
|
||||||
|
u = unpack('!BB5s', raw_bin_data)
|
||||||
|
return {'sec_domain': u[0], 'key_set_version': u[1], 'counter': u[2]}
|
||||||
|
|
||||||
|
class EF_SIM_AUTH_COUNTER(TransparentEF):
|
||||||
|
def __init__(self, fid='af24', name='EF.SIM_AUTH_COUNTER'):
|
||||||
|
super().__init__(fid, name=name, desc='Number of remaining RUN GSM ALGORITHM executions')
|
||||||
|
self._construct = Struct('num_run_gsm_algo_remain'/Int32ub)
|
||||||
|
|
||||||
|
class EF_GP_COUNT(LinFixedEF):
|
||||||
|
def __init__(self, fid='6f26', name='EF.GP_COUNT', desc='GP SCP02 Counters'):
|
||||||
|
super().__init__(fid, name=name, desc=desc, rec_len={5,5})
|
||||||
|
def _decode_record_bin(self, raw_bin_data):
|
||||||
|
u = unpack('!BBHB', raw_bin_data)
|
||||||
|
return {'sec_domain': u[0], 'key_set_version': u[1], 'counter': u[2], 'rfu': u[3]}
|
||||||
|
|
||||||
|
class EF_GP_DIV_DATA(LinFixedEF):
|
||||||
|
def __init__(self, fid='6f27', name='EF.GP_DIV_DATA', desc='GP SCP02 key diversification data'):
|
||||||
|
super().__init__(fid, name=name, desc=desc, rec_len={12,12})
|
||||||
|
def _decode_record_bin(self, raw_bin_data):
|
||||||
|
u = unpack('!BB8s', raw_bin_data)
|
||||||
|
return {'sec_domain': u[0], 'key_set_version': u[1], 'key_div_data': u[2].hex()}
|
||||||
|
|
||||||
|
class EF_SIM_AUTH_KEY(TransparentEF):
|
||||||
|
def __init__(self, fid='6f20', name='EF.SIM_AUTH_KEY'):
|
||||||
|
super().__init__(fid, name=name, desc='USIM authentication key')
|
||||||
|
CfgByte = BitStruct(Bit[2],
|
||||||
|
'use_sres_deriv_func_2'/Bit,
|
||||||
|
'use_opc_instead_of_op'/Bit,
|
||||||
|
'algorithm'/Enum(Nibble, milenage=4, comp128v1=1, comp128v2=2, comp128v3=3))
|
||||||
|
self._construct = Struct('cfg'/CfgByte,
|
||||||
|
'key'/Bytes(16),
|
||||||
|
'op'/If(this.cfg.algorithm=='milenage' and not this.cfg.use_opc_instead_of_op, Bytes(16)),
|
||||||
|
'opc'/If(this.cfg.algorithm=='milenage' and this.cfg.use_opc_instead_of_op, Bytes(16))
|
||||||
|
)
|
||||||
|
|
||||||
|
class DF_SYSTEM(CardDF):
|
||||||
|
def __init__(self):
|
||||||
|
super().__init__(fid='a515', name='DF.SYSTEM', desc='CardOS specifics')
|
||||||
|
files = [
|
||||||
|
EF_PIN('6f01', 'EF.CHV1'),
|
||||||
|
EF_PIN('6f81', 'EF.CHV2'),
|
||||||
|
EF_PIN('6f0a', 'EF.ADM1'),
|
||||||
|
EF_PIN('6f0b', 'EF.ADM2'),
|
||||||
|
EF_PIN('6f0c', 'EF.ADM3'),
|
||||||
|
EF_PIN('6f0d', 'EF.ADM4'),
|
||||||
|
EF_MILENAGE_CFG(),
|
||||||
|
EF_0348_KEY(),
|
||||||
|
EF_SIM_AUTH_COUNTER(),
|
||||||
|
EF_SIM_AUTH_KEY(),
|
||||||
|
EF_0348_COUNT(),
|
||||||
|
EF_GP_COUNT(),
|
||||||
|
EF_GP_DIV_DATA(),
|
||||||
|
]
|
||||||
|
self.add_files(files)
|
||||||
|
|
||||||
|
def decode_select_response(self, resp_hex):
|
||||||
|
return pySim.ts_102_221.decode_select_response(resp_hex)
|
||||||
|
|
||||||
|
class EF_USIM_SQN(TransparentEF):
|
||||||
|
def __init__(self, fid='af30', name='EF.USIM_SQN'):
|
||||||
|
super().__init__(fid, name=name, desc='SQN parameters for AKA')
|
||||||
|
Flag1 = BitStruct('skip_next_sqn_check'/Bit, 'delta_max_check'/Bit,
|
||||||
|
'age_limit_check'/Bit, 'sqn_check'/Bit,
|
||||||
|
'ind_len'/BitsInteger(4))
|
||||||
|
Flag2 = BitStruct('rfu'/BitsRFU(5), 'dont_clear_amf_for_macs'/Bit,
|
||||||
|
'aus_concealed'/Bit, 'autn_concealed'/Bit)
|
||||||
|
self._construct = Struct('flag1'/Flag1, 'flag2'/Flag2,
|
||||||
|
'delta_max'/BytesInteger(6), 'age_limit'/BytesInteger(6),
|
||||||
|
'freshness'/HexAdapter(GreedyBytes))
|
||||||
|
|
||||||
|
class EF_USIM_AUTH_KEY(TransparentEF):
|
||||||
|
def __init__(self, fid='af20', name='EF.USIM_AUTH_KEY'):
|
||||||
|
super().__init__(fid, name=name, desc='USIM authentication key')
|
||||||
|
CfgByte = BitStruct(Bit, 'only_4bytes_res_in_3g'/Bit,
|
||||||
|
'use_sres_deriv_func_2_in_3g'/Bit,
|
||||||
|
'use_opc_instead_of_op'/Bit,
|
||||||
|
'algorithm'/Enum(Nibble, milenage=4, sha1_aka=5, xor=15))
|
||||||
|
self._construct = Struct('cfg'/CfgByte,
|
||||||
|
'key'/Bytes(16),
|
||||||
|
'op'/If(this.cfg.algorithm=='milenage' and not this.cfg.use_opc_instead_of_op, Bytes(16)),
|
||||||
|
'opc'/If(this.cfg.algorithm=='milenage' and this.cfg.use_opc_instead_of_op, Bytes(16))
|
||||||
|
)
|
||||||
|
class EF_USIM_AUTH_KEY_2G(TransparentEF):
|
||||||
|
def __init__(self, fid='af22', name='EF.USIM_AUTH_KEY_2G'):
|
||||||
|
super().__init__(fid, name=name, desc='USIM authentication key in 2G context')
|
||||||
|
CfgByte = BitStruct(Bit, 'only_4bytes_res_in_3g'/Bit,
|
||||||
|
'use_sres_deriv_func_2_in_3g'/Bit,
|
||||||
|
'use_opc_instead_of_op'/Bit,
|
||||||
|
'algorithm'/Enum(Nibble, milenage=4, comp128v1=1, comp128v2=2, comp128v3=3))
|
||||||
|
self._construct = Struct('cfg'/CfgByte,
|
||||||
|
'key'/Bytes(16),
|
||||||
|
'op'/If(this.cfg.algorithm=='milenage' and not this.cfg.use_opc_instead_of_op, Bytes(16)),
|
||||||
|
'opc'/If(this.cfg.algorithm=='milenage' and this.cfg.use_opc_instead_of_op, Bytes(16))
|
||||||
|
)
|
||||||
|
class EF_GBA_SK(TransparentEF):
|
||||||
|
def __init__(self, fid='af31', name='EF.GBA_SK'):
|
||||||
|
super().__init__(fid, name=name, desc='Secret key for GBA key derivation')
|
||||||
|
self._construct = GreedyBytes
|
||||||
|
|
||||||
|
class EF_GBA_REC_LIST(TransparentEF):
|
||||||
|
def __init__(self, fid='af32', name='EF.GBA_REC_LIST'):
|
||||||
|
super().__init__(fid, name=name, desc='Secret key for GBA key derivation')
|
||||||
|
# integers representing record numbers in EF-GBANL
|
||||||
|
self._construct = GreedyRange(Int8ub)
|
||||||
|
|
||||||
|
class EF_GBA_INT_KEY(LinFixedEF):
|
||||||
|
def __init__(self, fid='af33', name='EF.GBA_INT_KEY'):
|
||||||
|
super().__init__(fid, name=name, desc='Secret key for GBA key derivation', rec_len={32,32})
|
||||||
|
self._construct = GreedyBytes
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class SysmocomSJA2(CardModel):
|
||||||
|
_atrs = [ "3B 9F 96 80 1F 87 80 31 E0 73 FE 21 1B 67 4A 4C 75 30 34 05 4B A9",
|
||||||
|
"3B 9F 96 80 1F 87 80 31 E0 73 FE 21 1B 67 4A 4C 75 31 33 02 51 B2",
|
||||||
|
"3B 9F 96 80 1F 87 80 31 E0 73 FE 21 1B 67 4A 4C 52 75 31 04 51 D5" ]
|
||||||
|
@classmethod
|
||||||
|
def add_files(cls, rs:RuntimeState):
|
||||||
|
"""Add sysmocom SJA2 specific files to given RuntimeState."""
|
||||||
|
rs.mf.add_file(DF_SYSTEM())
|
||||||
|
# optional USIM application
|
||||||
|
if 'a0000000871002' in rs.mf.applications:
|
||||||
|
usim_adf = rs.mf.applications['a0000000871002']
|
||||||
|
files_adf_usim = [
|
||||||
|
EF_USIM_AUTH_KEY(),
|
||||||
|
EF_USIM_AUTH_KEY_2G(),
|
||||||
|
EF_GBA_SK(),
|
||||||
|
EF_GBA_REC_LIST(),
|
||||||
|
EF_GBA_INT_KEY(),
|
||||||
|
EF_USIM_SQN(),
|
||||||
|
]
|
||||||
|
usim_adf.add_files(files_adf_usim)
|
||||||
|
# optional ISIM application
|
||||||
|
if 'a0000000871004' in rs.mf.applications:
|
||||||
|
isim_adf = rs.mf.applications['a0000000871004']
|
||||||
|
files_adf_isim = [
|
||||||
|
EF_USIM_AUTH_KEY(name='EF.ISIM_AUTH_KEY'),
|
||||||
|
EF_USIM_AUTH_KEY_2G(name='EF.ISIM_AUTH_KEY_2G'),
|
||||||
|
EF_USIM_SQN(name='EF.ISIM_SQN'),
|
||||||
|
]
|
||||||
|
isim_adf.add_files(files_adf_isim)
|
||||||
Reference in New Issue
Block a user