mirror of https://gerrit.osmocom.org/pysim
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:
parent
9edbdb93f7
commit
f44256c7df
|
@ -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.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_102_221 import CardProfileUICC
|
||||
from pySim.ts_31_102 import CardApplicationUSIM
|
||||
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
|
||||
|
||||
def init_card(sl):
|
||||
|
@ -84,6 +86,8 @@ def init_card(sl):
|
|||
rs.mf.add_file(DF_TELECOM())
|
||||
rs.mf.add_file(DF_GSM())
|
||||
|
||||
CardModel.apply_matching_models(scc, rs)
|
||||
|
||||
# inform the transport that we can do context-specific SW interpretation
|
||||
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 tempfile
|
||||
import json
|
||||
import abc
|
||||
import inspect
|
||||
|
||||
import cmd2
|
||||
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 smartcard.util import toBytes
|
||||
|
||||
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.exceptions import *
|
||||
from pySim.jsonpath import js_path_find, js_path_modify
|
||||
from pySim.commands import SimCardCommands
|
||||
|
||||
class CardFile(object):
|
||||
"""Base class for all objects in the smart card filesystem.
|
||||
|
@ -1427,3 +1432,30 @@ class CardProfile(object):
|
|||
Tuple of two strings
|
||||
"""
|
||||
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)
|
||||
|
|
|
@ -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)
|
Loading…
Reference in New Issue