aboutsummaryrefslogtreecommitdiffstats
path: root/pySim
diff options
context:
space:
mode:
authorHarald Welte <laforge@osmocom.org>2021-10-14 15:53:39 +0200
committerHarald Welte <laforge@osmocom.org>2021-10-14 16:41:57 +0200
commitf44256c7dff6d24f1f940d4ca71219abbb0c7e34 (patch)
tree6361a1b2efdc384faa7d68585737d43dce9d7dc2 /pySim
parent9edbdb93f7f1e37232fb38e2e465baea4ffcfaf2 (diff)
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
Diffstat (limited to 'pySim')
-rw-r--r--pySim/filesystem.py32
-rw-r--r--pySim/sysmocom_sja2.py241
2 files changed, 273 insertions, 0 deletions
diff --git a/pySim/filesystem.py b/pySim/filesystem.py
index 170429b..5cdac56 100644
--- a/pySim/filesystem.py
+++ b/pySim/filesystem.py
@@ -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)
diff --git a/pySim/sysmocom_sja2.py b/pySim/sysmocom_sja2.py
new file mode 100644
index 0000000..337eca7
--- /dev/null
+++ b/pySim/sysmocom_sja2.py
@@ -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)