| # coding=utf-8 |
| """Utilities / Functions related to sysmocom SJA2/SJA5 cards |
| |
| (C) 2021-2023 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)) |
| KeyLenAndType = BitStruct('mac_length'/Mapping(Bit, {8:0, 4:1}), |
| 'algorithm'/Enum(Bit, des=0, aes=1), |
| 'key_length'/MultiplyAdapter(BitsInteger(3), 8), |
| '_rfu'/BitsRFU(1), |
| 'key_type'/Enum(BitsInteger(2), kic=0, kid=1, kik=2, any=3)) |
| self._construct = Struct('security_domain'/Int8ub, |
| 'key_set_version'/Int8ub, |
| 'key_len_and_type'/KeyLenAndType, |
| 'key'/HexAdapter(Bytes(this.key_len_and_type.key_length))) |
| |
| |
| 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, **kwargs): |
| 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, **kwargs): |
| 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, **kwargs): |
| 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(Padding(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'/HexAdapter(Bytes(16)), |
| 'op_opc' /HexAdapter(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.CardProfileUICC.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'/GreedyRange(BytesInteger(6))) |
| |
| |
| 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') |
| Algorithm = Enum(Nibble, milenage=4, sha1_aka=5, tuak=6, xor=15) |
| CfgByte = BitStruct(Padding(1), 'only_4bytes_res_in_3g'/Bit, |
| 'sres_deriv_func_2_in_3g'/Mapping(Bit, {1:0, 2:1}), |
| 'use_opc_instead_of_op'/Mapping(Bit, {False:0, True:1}), |
| 'algorithm'/Algorithm) |
| self._construct = Struct('cfg'/CfgByte, |
| 'key'/HexAdapter(Bytes(16)), |
| 'op_opc' /HexAdapter(Bytes(16))) |
| # TUAK has a rather different layout for the data, so we define a different |
| # construct below and use explicit _{decode,encode}_bin() methods for separating |
| # the TUAK and non-TUAK situation |
| CfgByteTuak = BitStruct(Padding(1), |
| 'key_length'/Mapping(Bit, {128:0, 256:1}), |
| 'sres_deriv_func_in_3g'/Mapping(Bit, {1:0, 2:1}), |
| 'use_opc_instead_of_op'/Mapping(Bit, {False:0, True:1}), |
| 'algorithm'/Algorithm) |
| TuakCfgByte = BitStruct(Padding(1), |
| 'ck_and_ik_size'/Mapping(Bit, {128:0, 256:1}), |
| 'mac_size'/Mapping(BitsInteger(3), {64:0, 128:1, 256:2}), |
| 'res_size'/Mapping(BitsInteger(3), {32:0, 64:1, 128:2, 256:3})) |
| self._constr_tuak = Struct('cfg'/CfgByteTuak, |
| 'tuak_cfg'/TuakCfgByte, |
| 'num_of_keccak_iterations'/Int8ub, |
| 'op_opc'/HexAdapter(Bytes(32)), |
| 'k'/HexAdapter(Bytes(32))) |
| |
| def _decode_bin(self, raw_bin_data: bytearray) -> dict: |
| if raw_bin_data[0] & 0x0F == 0x06: |
| return parse_construct(self._constr_tuak, raw_bin_data) |
| else: |
| return parse_construct(self._construct, raw_bin_data) |
| |
| def _encode_bin(self, abstract_data: dict) -> bytearray: |
| if abstract_data['cfg']['algorithm'] == 'tuak': |
| return self._constr_tuak.build(abstract_data) |
| else: |
| return self._construct.build(abstract_data) |
| |
| |
| 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(Padding(1), '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, xor=14)) |
| self._construct = Struct('cfg'/CfgByte, |
| 'key'/HexAdapter(Bytes(16)), |
| 'op_opc' /HexAdapter(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) |
| |
| class SysmocomSJA5(CardModel): |
| _atrs = ["3B 9F 96 80 1F 87 80 31 E0 73 FE 21 1B 67 4A 35 75 30 35 02 51 CC", |
| "3B 9F 96 80 1F 87 80 31 E0 73 FE 21 1B 67 4A 35 75 30 35 02 65 F8", |
| "3B 9F 96 80 1F 87 80 31 E0 73 FE 21 1B 67 4A 35 75 30 35 02 59 C4"] |
| |
| @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) |