#
# Defines things we know about ISO-7816 cards and GSM cards.
#
#
# License
# -------
#
# Copyright (c) 2007,2014,2015,2016,2018,2020,2021 Russell Stuart.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published
# by the Free Software Foundation, either version 3 of the License, or (at
# your option) any later version.
#
# The copyright holders grant you an additional permission under Section 7
# of the GNU Affero General Public License, version 3, exempting you from
# the requirement in Section 6 of the GNU General Public License, version 3,
# to accompany Corresponding Source with Installation Information for the
# Program or any work based on the Program. You are still required to
# comply with all other Section 6 requirements to provide Corresponding
# Source.
#
# 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
# Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see .
#
import binascii
import string
import sys
#
# Python2/3 compatibility stuff.
#
# b2a_hex: convert binary to a hex string.
# num8: Convert a byte to a number.
# to_bytes: Convert a sequence of numbers to bytes.
#
if sys.hexversion >= 0x03000000:
num8 = int
to_bytes = bytes
#
# ffs. What idiot decided 'b2a_hex()' shouldn't return a string?
#
def b2a_hex(hex):
return binascii.b2a_hex(hex).decode('latin')
else:
num8 = ord
b2a_hex = binascii.b2a_hex
#
# Do what Python3's "bytes(sequence)" does.
#
def to_bytes(ints):
return ''.join(chr(i) for i in ints)
def num16(bytes):
return num8(bytes[0]) * 256 + num8(bytes[1])
#
# Parameters for read() and seek().
#
RECORD_MODE_NEXT = 0x02
RECORD_MODE_PREVIOUS = 0x03
RECORD_MODE_SEEK = 0x04
SEEK_MODE_BEGINING_FORWARD = 0x00
SEEK_MODE_BEGINING_BACKWARD = 0x01
SEEK_MODE_NEXT_FORWARD = 0x02
SEEK_MODE_NEXT_BACKWARD = 0x03
SEEK_TYPE_GET = 0x10
#
# Access conditions / Pin names / Keys / whatever name they have today.
#
ACCESS_CONDITION = {
0: 'ALW',
1: 'PN1',
2: 'PN2',
3: 'RFU',
4: 'A04',
5: 'A05',
6: 'A06',
7: 'NE7',
8: 'A08',
9: 'A09',
10: 'A0a',
11: 'A0b',
12: 'A0c',
13: 'A0d',
14: 'A0e',
15: 'NEF',
}
#
# 7816 instructions.
#
INS_ERASE_BINARY = 0x0e
INS_VERIFY = 0x20
INS_MANAGE_CHANNEL = 0x70
INS_EXTERNAL_AUTH = 0x82
INS_GET_CHALLENGE = 0x84
INS_INTERNAL_AUTH = 0x88
INS_SELECT_FILE = 0xa4
INS_READ_BINARY = 0xb0
INS_READ_RECORD = 0xb2
INS_GET_RESPONSE = 0xc0
INS_ENVELOPE = 0xc2
INS_GET_DATA = 0xca
INS_WRITE_BINARY = 0xd0
INS_WRITE_RECORD = 0xdc
INS_UPDATE_BINARY = 0xd6
INS_PUT_DATA = 0xda
INS_UPDATE_RECORD = 0xdc
INS_APPEND_RECORD = 0xe2
#
# P2 encoding for read/write/update records.
#
RECORD_P2_1ST_RECNO = 0x00
RECORD_P2_LAST_RECNO = 0x01
RECORD_P2_NEXT_RECNO = 0x02
RECORD_P2_PREV_RECNO = 0x03
RECORD_P2_RECNO = 0x04
RECORD_P2_FROM_RECNO = 0x05
RECORD_P2_TO_RECNO = 0x06
RECORD_P2_RFU = 0x07
#
# GSM instructions.
#
CLS_GSM_SIM = 0xa0
CLS_GSM_USIM = 0x00
INS_GSM_INVALIDATE = 0x04
INS_GSM_TERM_PROFILE = 0x10
INS_GSM_FETCH = 0x12
INS_GSM_TERM_RESPONSE = 0x14
INS_GSM_VERIFY_CHV = INS_VERIFY
INS_GSM_CHANGE_CHV = 0x24
INS_GSM_DISABLE_CHV = 0x26
INS_GSM_ENABLE_CHV = 0x28
INS_GSM_UNBLOCK_CHV = 0x2C
INS_GSM_INCREASE = 0x32
INS_GSM_REHABILITATE = 0x44
INS_GSM_MANAGE_CHANNEL = INS_MANAGE_CHANNEL
INS_GSM_RUN_GSM = INS_INTERNAL_AUTH
INS_GSM_SEEK = 0xa2
INS_GSM_SELECT_FILE = INS_SELECT_FILE
INS_GSM_READ_BINARY = INS_READ_BINARY
INS_GSM_READ_RECORD = INS_READ_RECORD
INS_GSM_GET_RESPONSE = INS_GET_RESPONSE
INS_GSM_ENVELOPE = INS_ENVELOPE
INS_GSM_UPDATE_BINARY = INS_UPDATE_BINARY
INS_GSM_UPDATE_RECORD = INS_UPDATE_RECORD
INS_GSM_STATUS = 0xf2
#
# 7816-9 AM_DO encoding.
#
AM_DO_TAG_SAC_MASK = 0xf0
AM_DO_TAG_SAC = 0x80
AM_DO_TAG_CI12 = 0x0f
AM_DO_TAG_CI12_P2 = 0x01
AM_DO_TAG_CI12_P1 = 0x02
AM_DO_TAG_CI12_INS = 0x04
AM_DO_TAG_CI12_CLS = 0x08
AM_DO_TAG_VENDOR = 0x9c
AM_DO_SAC_ALWAYS = 0x00
AM_DO_SAC_NEVER = 0xff
AM_DO_SAC_ID = 0x0f
AM_DO_SAC_ID_ALWAYS = 0x00
AM_DO_SAC_ID_PIN1 = 0x01
AM_DO_SAC_ID_PIN2 = 0x02
AM_DO_SAC_ID_ADM1 = 0x03
AM_DO_SAC_ID_ADM2 = 0x04
AM_DO_SAC_ID_ADM3 = 0x05
AM_DO_SAC_ID_ADM4 = 0x06
AM_DO_SAC_ID_ADM5 = 0x07
AM_DO_SAC_ID_ADM6 = 0x08
AM_DO_SAC_ID_ADM7 = 0x09
AM_DO_SAC_ID_ADM8 = 0x0a
AM_DO_SAC_ID_ADM9 = 0x0b
AM_DO_SAC_ID_ADM10 = 0x0c
AM_DO_SAC_ID_NEVER = 0x0f
AM_DO_SAC_COND = 0x70
AM_DO_SAC_COND_SECURE = 0x40
AM_DO_SAC_ORAND = 0x80
AM_DO_SAC_OR = 0x00
AM_DO_SAC_AND = 0x80
AM_DO_DF_DELETE_CHILD = 0x01
AM_DO_DF_CREATE_EF = 0x02
AM_DO_DF_CREATE = 0x04
AM_DO_DF_DEACTIVATE = 0x08
AM_DO_DF_ACTIVATE = 0x10
AM_DO_DF_TERMINATE = 0x20
AM_DO_DF_DELETE_SELF = 0x40
AM_DO_EF_READ = 0x01
AM_DO_EF_UPDATE = 0x02
AM_DO_EF_RFU0 = 0x04
AM_DO_EF_DEACTIVATE = 0x08
AM_DO_EF_ACTIVATE = 0x10
AM_DO_EF_TERMINATE = 0x20
AM_DO_EF_GSM_INCREASE = 0x20
AM_DO_EF_DELETE_SELF = 0x40
AM_DO_KY_READ = 0x01
AM_DO_KY_UPDATE = 0x02
AM_DO_KY_MSO_PSO = 0x04
AM_DO_KY_DEACTIVATE = 0x08
AM_DO_KY_ACTIVATE = 0x10
AM_DO_KY_TERMINATE = 0x20
AM_DO_KY_DELETE_SELF = 0x40
AM_DO_SE_READ = 0x01
AM_DO_SE_UPDATE = 0x02
AM_DO_SE_MSO_RESTORE = 0x04
AM_DO_SE_DEACTIVATE = 0x08
AM_DO_SE_ACTIVATE = 0x10
AM_DO_SE_TERMINATE = 0x20
AM_DO_SE_DELETE_SELF = 0x40
#
# 7816-9 SC_DO encoding.
#
SC_DO_TAG_ALWAYS = 0x90
SC_DO_TAG_NEVER = 0x97
SC_DO_TAG_SAC = 0x9e
SC_DO_TAG_OR = 0xa0
SC_DO_TAG_CRT = 0xa4
SC_DO_TAG_AND = 0xaf
SC_CRT_TAG_KEY = 0x83
SC_CRT_TAG_ACTION = 0x95
SC_CRT_ACTION_AUTH = 0x80
SC_CRT_ACTION_VERIFY = 0x08
#
# PIN & Key cross reference.
#
GSM_KEY_2_PIN = [
(0x00, 0x00, AM_DO_SAC_ID_ALWAYS, 0),
(0x01, 0x08, AM_DO_SAC_ID_PIN1, 0),
(0x09, 0x0e, AM_DO_SAC_ID_ADM1, 1),
(0x11, 0x11, AM_DO_SAC_ID_PIN1, 0),
(0x81, 0x88, AM_DO_SAC_ID_PIN2, 0),
(0x89, 0x8e, AM_DO_SAC_ID_ADM6, 1),
]
GSM_KEY_2_PIN = dict(
(k, p + (k - s) * i)
for s, f, p, i in GSM_KEY_2_PIN for k in range(s, f + 1))
#
# File types.
#
FILE_TYPE_DF = 0x38
FILE_TYPE_EF = 0x00
#
# File statuses
#
FILE_STATUS_VALID = 0x01
FILE_STATUS_ACCESSIBLE_INVALID = 0x04
#
# UICC Characteristics.
#
FILE_CHARACTERISTICS_CLOCK = 0x0D
FILE_CHARACTERISTICS_CLOCK_STOP = 0x01
FILE_CHARACTERISTICS_CLOCK_HIGH = 0x04
FILE_CHARACTERISTICS_CLOCK_LOW = 0x08
FILE_CHARACTERISTICS_MHZ = 0x02
FILE_CHARACTERISTICS_MHZ_13_8 = 0x00
FILE_CHARACTERISTICS_MHZ_13_4 = 0x02
FILE_CHARACTERISTICS_VOLTS = 0x70
FILE_CHARACTERISTICS_VOLTS_5V = 0x00
FILE_CHARACTERISTICS_VOLTS_3V = 0x10
FILE_CHARACTERISTICS_VOLTS_18V = 0x20
FILE_CHARACTERISTICS_VOLTS_FUTURE = 0x40
FILE_CHARACTERISTICS_CHV1 = 0x80
#
# File structures.
#
FILE_STRUCTURE_UNKNOWN = 0x00
FILE_STRUCTURE_TRANSPARENT = 0x01
FILE_STRUCTURE_LINEAR = 0x02
FILE_STRUCTURE_LINEAR_TLV = 0x03
FILE_STRUCTURE_LINEAR_V = 0x04
FILE_STRUCTURE_LINEAR_V_TLV = 0x05
FILE_STRUCTURE_CYCLIC = 0x06
FILE_STRUCTURE_CYCLIC_TLV = 0x07
#
# Life cycle encoding.
#
FILE_LIFECYCLE_UNKNOWN = 0x00
FILE_LIFECYCLE_CREATED = 0x01
FILE_LIFECYCLE_INITIALISED = 0x02
FILE_LIFECYCLE_ACTIVE_MASK = 0xf5
FILE_LIFECYCLE_ACTIVE_VALUE = 0x05
FILE_LIFECYCLE_DEACTIVE_VALUE = 0x04
FILE_LIFECYCLE_TERM_MASK = 0xfc
FILE_LIFECYCLE_TERM_VALUE = 0x0c
#
# Get the description of a SMART CARD APDU status.
#
def status2desc(status):
'''Return a description of the passed smart card status code'''
desc = STATUSES.get(status[-2:], None)
if desc is None:
desc = STATUSES.get(status[-2:-1], None)
if desc is None:
desc = '???'
elif '%' in desc:
desc = desc % num8(status[-1])
return b2a_hex(status[-2:]) + ' ' + desc
#
# Generate an AID useful for prefix searching. The new order is:
#
# - 2 bytes. Application class.
# - 5 bytes. Vendor.
# - 11 bytes. Application ID.
#
def aid_prefix(prefix):
if len(prefix) > 7:
return prefix[5:7] + prefix[0:5] + prefix[7:]
if len(prefix) > 2:
return prefix[-2:] + prefix[:-2]
return prefix
#
# Well known statuses.
#
STATUS_SUCCESS = b'\x90\x00'
STATUS_DATA_INVALID = b'\x69\x84'
STATUS_DATA_WAITING = b'\x61'
STATUS_DF_NOT_FOUND = b'\x6a\x82'
STATUS_FILE_INVALID = b'\x62\x83'
STATUS_FILE_NOT_FOUND = b'\x94\x04'
STATUS_INVALID_ADDRESS = b'\x94\x02'
STATUS_NOSUCH_FILE = b'\x6a\x82'
STATUS_PIN_REQUIRED = b'\x69\x82'
STATUS_SUCCESS_DATA = b'\x9f\x00'
STATUSES = {
binascii.a2b_hex('9000'): 'Success',
binascii.a2b_hex('91'): 'Success, response of %d bytes waiting',
binascii.a2b_hex('9E'): 'download error, response of %d bytes waiting',
binascii.a2b_hex('9F'): 'Success, response of %d bytes waiting',
binascii.a2b_hex('9200'): 'Success after 0 retries',
binascii.a2b_hex('9201'): 'Success after 1 retries',
binascii.a2b_hex('9202'): 'Success after 2 retries',
binascii.a2b_hex('9203'): 'Success after 3 retries',
binascii.a2b_hex('9204'): 'Success after 4 retries',
binascii.a2b_hex('9205'): 'Success after 5 retries',
binascii.a2b_hex('9206'): 'Success after 6 retries',
binascii.a2b_hex('9207'): 'Success after 7 retries',
binascii.a2b_hex('9208'): 'Success after 8 retries',
binascii.a2b_hex('9209'): 'Success after 9 retries',
binascii.a2b_hex('9240'): 'Memory problem',
binascii.a2b_hex('9400'): 'No EF Selected',
binascii.a2b_hex('9402'): 'invalid address, or out of range',
binascii.a2b_hex('9404'): 'file or pattern not found',
binascii.a2b_hex('9408'): 'file inconsistant with command',
binascii.a2b_hex('9802'): 'no CHV initialised',
binascii.a2b_hex('9804'): 'no permission or CHV wrong',
binascii.a2b_hex('9808'): 'in contradiction with CHV status',
binascii.a2b_hex('9810'): 'in contradiction with invalidation status',
binascii.a2b_hex('9840'): 'unsuccessful CHV validation, no attempts left',
binascii.a2b_hex('9850'): 'max value reached',
binascii.a2b_hex('9850'): 'max value reached',
binascii.a2b_hex('61'): '%d response bytes remaining',
binascii.a2b_hex('62'): 'execution error, memory unchanged',
binascii.a2b_hex('6281'): 'execution error, memory unchanged - returned data corrupted',
binascii.a2b_hex('6282'): 'execution error, memory unchanged - returned data short',
binascii.a2b_hex('6283'): 'execution error, memory unchanged - selected file invalid',
binascii.a2b_hex('6284'): 'execution error, memory unchanged - FCI format invalid',
binascii.a2b_hex('63'): 'execution error, memory changed',
binascii.a2b_hex('6381'): 'execution error, memory changed - file filled up',
binascii.a2b_hex('63C0'): 'execution error, memory changed - counter 0',
binascii.a2b_hex('63C1'): 'execution error, memory changed - counter 1',
binascii.a2b_hex('63C2'): 'execution error, memory changed - counter 2',
binascii.a2b_hex('63C3'): 'execution error, memory changed - counter 3',
binascii.a2b_hex('63C4'): 'execution error, memory changed - counter 4',
binascii.a2b_hex('63C5'): 'execution error, memory changed - counter 5',
binascii.a2b_hex('63C6'): 'execution error, memory changed - counter 6',
binascii.a2b_hex('63C7'): 'execution error, memory changed - counter 7',
binascii.a2b_hex('63C8'): 'execution error, memory changed - counter 8',
binascii.a2b_hex('63C9'): 'execution error, memory changed - counter 9',
binascii.a2b_hex('63CA'): 'execution error, memory changed - counter 10',
binascii.a2b_hex('63CB'): 'execution error, memory changed - counter 11',
binascii.a2b_hex('63CC'): 'execution error, memory changed - counter 12',
binascii.a2b_hex('63CD'): 'execution error, memory changed - counter 13',
binascii.a2b_hex('63CE'): 'execution error, memory changed - counter 14',
binascii.a2b_hex('63CF'): 'execution error, memory changed - counter 15',
binascii.a2b_hex('64'): 'execution error, memory unchanged',
binascii.a2b_hex('65'): 'execution error, memory unchanged',
binascii.a2b_hex('6581'): 'execution error, memory unchanged - memory failure',
binascii.a2b_hex('66'): 'security related issue',
binascii.a2b_hex('67'): 'wrong length',
binascii.a2b_hex('68'): 'invalid function for class',
binascii.a2b_hex('6881'): 'invalid function for class - channel not supported',
binascii.a2b_hex('6882'): 'invalid function for class - secure messaging not supported',
binascii.a2b_hex('69'): 'command not allowed',
binascii.a2b_hex('6981'): 'command not allowed - incompatible with file structure',
binascii.a2b_hex('6982'): 'command not allowed - security not satisified',
binascii.a2b_hex('6983'): 'command not allowed - authention method blocked',
binascii.a2b_hex('6984'): 'command not allowed - referenced data invalidated',
binascii.a2b_hex('6985'): 'command not allowed - conditions of use not satisified',
binascii.a2b_hex('6986'): 'command not allowed - no current EF',
binascii.a2b_hex('6987'): 'command not allowed - expected SM data objects missing',
binascii.a2b_hex('6988'): 'command not allowed - SM data objects incorrect',
binascii.a2b_hex('6A'): 'incorrect P1 or P2',
binascii.a2b_hex('6A80'): 'incorrect P1 or P2 - incorrect parameters in the data field',
binascii.a2b_hex('6A81'): 'incorrect P1 or P2 - function not supported',
binascii.a2b_hex('6A82'): 'incorrect P1 or P2 - file not found',
binascii.a2b_hex('6A83'): 'incorrect P1 or P2 - record not found',
binascii.a2b_hex('6A84'): 'incorrect P1 or P2 - not enough memory space in the file',
binascii.a2b_hex('6A85'): 'incorrect P1 or P2 - data length inconsistent with TLV',
binascii.a2b_hex('6A86'): 'incorrect P1 or P2 - incorrect P1-P2',
binascii.a2b_hex('6A87'): 'incorrect P1 or P2 - length inconsistent with P1-P2',
binascii.a2b_hex('6A88'): 'incorrect P1 or P2 - referenced data not found',
binascii.a2b_hex('6B'): 'incorrect P1 or P2 parameter',
binascii.a2b_hex('6C'): 'incorrect LE paramater, %d is correct length',
binascii.a2b_hex('6D'): 'incorrect instruction code',
binascii.a2b_hex('6E'): 'incorrect instruction class',
binascii.a2b_hex('6F'): 'it didn\'t work, and the card isn\'t saying why',
}
#
# Decode a ber-tlv string.
#
def ber_tlv(blob, index=None, end=None):
if index is None:
index = 0
if end is None:
end = len(blob)
result = []
while index < end and blob[index] in b'\x00\xff':
index += 1
while index < end:
start = index
index += 1
if num8(blob[start]) & 0x1f == 0x1f:
while num8(blob[index]) & 0x80 == 0x80:
index += 1
index += 1
tag = blob[start:index]
length = 0
while num8(blob[index]) & 0x80 != 0:
length = (length << 7) + (num8(blob[index]) & 0x7f)
index += 1
length = (length << 7) + num8(blob[index])
index += 1
start = index
index += length
if num8(tag[0]) & 0x20 == 0:
result.append((tag, blob[start:index]))
else:
result.append((tag, ber_tlv(blob, start, index)[0]))
while index < end and blob[index] in b'\x00\xff':
index += 1
return result, index
#
# Decode a simple-tlv string.
#
def simple_tlv(blob, index=None, end=None):
if index is None:
index = 0
if end is None:
end = len(blob)
result = []
while index < end and blob[index] in b'\x00\xff':
index += 1
while index < end:
tag = blob[index]
index += 1
if blob[index] not in b'\xff':
length = num8(blob[index])
index += 1
else:
length = num16(blob[index + 1:index + 3])
index += 3
start = index
index += length
result.append((tag, blob[start:index]))
while index < end and blob[index] in b'\x00\xff':
index += 1
return result, index
#
# Decode a compact-tlv string.
#
def compact_tlv(blob, index=None, end=None):
if index is None:
index = 0
if end is None:
end = len(blob)
result = []
while index < end:
tag = 0x40 | (num8(blob[index]) >> 4)
length = num8(blob[index]) & 0xf
index += 1
start = index
index += length
result.append((tag, blob[start:index]))
return result, index
#
# Decode the response to the INS_SELECT_FILE APDU.
#
class Select:
#
# Instance variables. This what the response is unpacked into.
#
data = None # string, the raw data returned
access = None # dict, {AM_DO_EF_*:AM_DO_SAC_ID_PIN1, ...}
characteristics = None # int, Combination of FILE_CHARACTERISTICS_*
dfname = None # string, the dfname of a DF for USIM's or ""
ef_arr = None # list, List of er_arr file names referenced
file_size = None # int, memory occuped by file data, -1 for DF's
file_type = None # int, One of the FILE_TYPE_* constants
id = None # int, file id
lifecycle = None # int, One of FILE_LIFECYCLE_*
pins = None # int, 0=No pin, 1=PIN1, 2=PIN2, 3=PIN1+PIN2
reccnt = None # int, No. records in file, or -1 if not defined
reclen = None # int, the record length, or -1 if not defined
short_id = None # int, short id, or -1 if not defined
structure = None # int, One of the FILE_STRUCTURE_* constants
#
# Initialise the fields in here from a
# smart card response to the SELECT APDU.
#
def __init__(self, data, ef_arr=None):
self.data = data
if not data:
return
self.access = {}
self.ef_arr = []
if data[0] in b'\x00\x61':
self._init_fci()
elif data[0] in b'\x62':
self._init_fcp(ef_arr)
#
# The new style: a file control protocol. A list of TLV's.
#
def _init_fcp(self, ef_arr=None):
self.rfu = tuple()
for tag, value in ber_tlv(self.data)[0][0][1]:
#
# Tags from iso7816.
#
if tag == b'\x80':
self.file_size = num16(value)
elif tag == b'\x81':
pass
elif tag == b'\x82':
byte = num8(value[0])
fileType = byte & 0xb8
self.file_type = self.FILE_TYPES.get(fileType, -fileType - 1)
self.structure = byte & 0x80 != 0 and byte or byte & 0x07
if len(value) < 3:
pass
elif len(value) == 3:
self.reclen = num8(value[2])
else:
self.reclen = num16(value[2:4])
if len(value) < 5:
pass
else:
self.reccnt = num8(value[4])
elif tag == b'\x83':
self.id = num16(value)
if self.short_id is None:
self.short_id = self.id & 0x1f
elif tag == b'\x84':
self.dfname = value
#
# Additional tags from etsi TS 102 221 (selection 11.1.1).
#
elif tag == b'\x88':
if len(value) == 0:
self.short_id = -1
else:
self.short_id = num8(value[0]) >> 3
elif tag == b'\x8A':
self.lifecycle = num8(value[0])
elif tag == b'\x8B':
file_id = value[:2]
if file_id not in self.ef_arr:
self.ef_arr.append(file_id)
ef_arr_file = ef_arr and ef_arr.get(file_id, None) or None
if ef_arr_file and len(value) == 3:
self.parse_am_do(ber_tlv(ef_arr_file[num8(value[2])])[0])
elif tag == b'\x8C':
ams = [
i for i in range(7, -1, -1)
if (1 << i) & num8(value[0])]
for am, sc in zip(ams, value[1:]):
self.setaccess(am, sc)
elif tag == b'\xa5':
for tag1, value1 in value:
if tag1 == b'\x80':
self.characteristics = num8(value1[0])
elif tag == b'\xAB':
self.parse_am_do(value)
elif tag == b'\xC6':
self.pins = 0
for tag1, value1 in ber_tlv(value)[0]:
if tag1 == b'\x83':
self.pins |= self.PINS.get(num8(value1[0]), 0)
FILE_TYPES = {0x00: FILE_TYPE_EF, 0x38: FILE_TYPE_DF}
PINS = {AM_DO_SAC_ID_PIN1: 0x01, AM_DO_SAC_ID_PIN2: 0x02}
#
# The old style.
#
def _init_fci(self):
if len(self.data) < 7:
return
self.id = num16(self.data[4:6])
fileType = num8(self.data[6])
self.file_type = self.FCI_FILE_TYPES.get(fileType, -fileType - 1)
if self.file_type == FILE_TYPE_EF:
self._init_fci_ef()
elif self.file_type == FILE_TYPE_DF:
result = self._init_fci_df()
FCI_FILE_TYPES = {
1: FILE_TYPE_DF, # MF
2: FILE_TYPE_DF, # DF
4: FILE_TYPE_EF,
}
#
# Decode DF data for the old style.
#
def _init_fci_df(self):
self.rfu = [(0, 2), (7, 12), (18, 19), (22, len(self.data))]
if len(self.data) < 22:
return b2a_hex(self.data)
self.pins = 0
self.pins += num8(self.data[18]) & 0x80 == 0 and 0 or 0x01
self.pins += num8(self.data[20]) & 0x80 == 0 and 0 or 0x02
self.characteristics = num8(self.data[13])
#
# Decode EF data for the old style.
#
def _init_fci_ef(self):
self.rfu = [(0, 2), (15, len(self.data))]
if len(self.data) < 14:
return
self.file_size = num16(self.data[2:4])
self.characteristics = num8(self.data[7])
self.access[AM_DO_EF_UPDATE] = num8(self.data[8]) % 16
self.access[AM_DO_EF_READ] = num8(self.data[8]) // 16
self.access[AM_DO_EF_RFU0] = num8(self.data[9]) % 16
self.access[AM_DO_EF_GSM_INCREASE] = num8(self.data[9]) // 16
self.access[AM_DO_EF_DEACTIVATE] = num8(self.data[10]) % 16
self.access[AM_DO_EF_ACTIVATE] = num8(self.data[10]) // 16
status = num8(self.data[11])
self.lifecycle = self.LIFECYCLES.get(status, -status - 1)
struct = num8(self.data[13])
self.structure = self.STRUCTURES.get(struct, -struct - 1)
if len(self.data) >= 15:
self.reclen = num8(self.data[14])
else:
self.reclen = -1
LIFECYCLES = {
0x01: FILE_LIFECYCLE_ACTIVE_VALUE,
0x41: FILE_LIFECYCLE_ACTIVE_VALUE,
}
STRUCTURES = {
0x00: FILE_STRUCTURE_TRANSPARENT,
0x01: FILE_STRUCTURE_LINEAR,
0x03: FILE_STRUCTURE_CYCLIC
}
#
# Print a string representation of the SELECT response.
#
def __str__(self, short=None):
if len(self.data) < 7:
return b2a_hex(self.data)
str_id = self._str_undef(self.id, '%04x')
if self.short_id is None:
str_id += ' '
pass
elif self.short_id == -1:
str_id += ': '
else:
str_id += ':%02x' % self.short_id
str_file_type = self._str_lookup(
self.STR_FILETYPE, self.file_type, 'x%02x')
if self.file_type == FILE_TYPE_DF:
result = self._str_df()
elif self.file_type == FILE_TYPE_EF:
result = self._str_ef()
else:
result = b2a_hex(self.data)
return '%s %s %s' % (str_id, str_file_type, result)
STR_FILETYPE = {
FILE_TYPE_DF: 'DF',
FILE_TYPE_EF: 'EF'
}
#
# Print the bits specific to DF or MF.
#
def _str_df(self):
if self.pins is None:
return b2a_hex(self.data)
str_pins = self._str_lookup(self.STR_PINS, self.pins, 'x%02x')
if self.dfname is None:
str_dfname = ''
else:
str_dfname = ' dfname=' + b2a_hex(self.dfname)
return 'pins=%s%s%s' % (str_pins, str_dfname, self._str_rfu())
STR_PINS = {
0: 'n',
1: '1',
2: '2',
3: '1+2',
}
#
# Print the bits specific to EF.
#
def _str_ef(self, short=None):
def ac(condition):
cond = self.access.get(condition, None)
dsc = cond is None and '???' or ACCESS_CONDITION[cond]
return dsc[0] + dsc[-1]
if self.characteristics is None:
return b2a_hex(self.data)
str_structure = self._str_lookup(
self.STR_STRUCTURE, self.structure, 'x%02x')
str_lifecycle = self._str_lookup(
self.STR_LIFECYCLE, self.lifecycle, ' life=x%02x')
if self.structure == FILE_STRUCTURE_TRANSPARENT:
if self.reclen in (None, -1, 0):
str_len = ''
else:
str_len = ' len=x%02x' % (self.reclen,)
elif self.structure in (FILE_STRUCTURE_LINEAR, FILE_STRUCTURE_CYCLIC):
str_len = ' len=%s:%s' % (
self._str_undef(self.reclen, '%d'),
self._str_undef(self.reccnt, '%d'))
else:
str_len = ' len=x%02x' % self.reclen
return '%s %s %s,%s,%s,%s,%s,%s%s%s%s' % (
self._str_undef(self.file_size, '%4d'), str_structure,
ac(AM_DO_EF_READ),
ac(AM_DO_EF_UPDATE),
ac(AM_DO_EF_GSM_INCREASE),
ac(AM_DO_EF_RFU0),
ac(AM_DO_EF_DEACTIVATE),
ac(AM_DO_EF_ACTIVATE),
str_len, str_lifecycle, self._str_rfu()
)
STR_STRUCTURE = {
FILE_STRUCTURE_TRANSPARENT: 'trn',
FILE_STRUCTURE_LINEAR: 'lin',
FILE_STRUCTURE_LINEAR_TLV: 'ltv',
FILE_STRUCTURE_LINEAR_V: 'vin',
FILE_STRUCTURE_LINEAR_V_TLV: 'vtv',
FILE_STRUCTURE_CYCLIC: 'cyc',
FILE_STRUCTURE_CYCLIC_TLV: 'ctv'
}
STR_LIFECYCLE = {
FILE_LIFECYCLE_ACTIVE_VALUE: '',
FILE_LIFECYCLE_ACTIVE_VALUE | 2: '',
}
#
# Dump the unused portions of the old style if they aren't 0.
#
def _str_rfu(self):
results = []
for u in self.rfu:
begin = u[0]
while begin < u[1]:
while begin < u[1] and self.data[begin] in b'\x00':
begin += 1
end = begin
while end < u[1] and self.data[end] not in b'\x00':
end += 1
if begin < u[1]:
if begin + 1 == end:
results.append(
'%d=%s' %
(begin, b2a_hex(self.data[begin:end])))
else:
results.append(
'%d:%d=%s' %
(begin, end, b2a_hex(self.data[begin:end])))
begin = end
if not results:
return ''
return ' ' + ' '.join(results)
#
# Lookup a value.
#
def _str_lookup(self, table, value, format):
result = table.get(value, None)
if result is not None:
return result
return self._str_undef(value, format)
#
# Print an undefined value.
#
def _str_undef(self, value, format):
if value is None:
return '?'
if value < 0:
value = 1 - value
return format % value
#
# Compare two selects.
#
def __cmp__(self, other):
return cmp(self.data, other.data)
def __eq__(self, other):
return self.data.__eq__(other.data)
def __ne__(self, other):
return self.data.__ne__(other.data)
def __lt__(self, other):
return self.data.__lt__(other.data)
def __le__(self, other):
return self.data.__le__(other.data)
def __ge__(self, other):
return self.data.__ge__(other.data)
def __gt__(self, other):
return self.data.__gt__(other.data)
#
# Set self.access.
#
def setaccess(self, ams, sc):
for am in (1 << bit for bit in range(0, 8) if ams & (1 << bit) != 0):
if self.access.get(am, 9999) > sc:
self.access[am] = sc
def setaccess_crt(self, tag, am, value):
num8_tag = num8(tag[0])
if num8_tag == SC_DO_TAG_ALWAYS:
self.setaccess(am, AM_DO_SAC_ID_ALWAYS)
elif num8_tag == SC_DO_TAG_NEVER:
self.setaccess(am, AM_DO_SAC_ID_NEVER)
elif num8_tag == SC_CRT_TAG_KEY:
self.setaccess(am, GSM_KEY_2_PIN[num8(value[0])])
elif tag in self.CONTAINER_TAGS:
for tag1, value1 in value:
self.setaccess_crt(tag1, am, value1)
CONTAINER_TAGS = to_bytes((SC_DO_TAG_CRT, SC_DO_TAG_OR, SC_DO_TAG_AND,))
def parse_am_do(self, value):
for tag, value in value:
tag_num = num8(tag[0])
if tag_num == AM_DO_TAG_VENDOR:
continue
if tag_num == AM_DO_TAG_SAC:
am = num8(value[0])
elif (tag_num & AM_DO_TAG_SAC_MASK) == AM_DO_TAG_SAC:
#
# Specify which commands can be applied by one of more of
# the instruction bytes CLA,INS,P1,P2 specfied by the
# AM_DO_TAG_CI12 mask. This isn't easily translated to
# the old GSM permissions, so we don't handle this.
#
am = -1
elif am != -1:
self.setaccess_crt(tag, am, value)
#
# Well known File ID's.
#
FILE_ID_EF_DIR = 0x2F00 # Application Directory
FILE_ID_EF_ATR = 0x2F01 # Answer to Reset
FILE_ID_MASTER = 0x3F00 # Root
FILE_ID_CURRENT_DF = 0x3FFF # Alias for current DF (not GSM)
FILE_ID_CURRENT_ADF = 0x7FFF # Alias for current ADF (GSM only)
FILE_ID_RFU = 0xFFFF # Reserved
#
# A set of classes that decode and encode binary data from a smart card.
# Here 'decode' means pretty print. The classes are initialised with a
# string that specifies the record format. An instance of the class
# decodes and encodes the binary data according to that format.
#
# Some formats require counts to specify a repeat factor or size. Counts can
# be one of:
#
# N - A fixed count. The number N is the count.
#
# =N - An indexed count. The count is contained in the byte at offset N
# in the record. Thus =0 means the count is in the first byte in
# the record.
#
# +N - An indexed count. The count is contained in the byte N bytes
# from the end. Thus +1 means the count is in the last byte in
# the record.
#
# -N - An indexed count. The count is contained in the byte N bytes
# before the current offset.
#
# * - A variable count. The count is the number of bytes needed to make
# up the record size. There can only be one of these, and it must
# follow any indexed counts.
#
class FormatClass:
formatters = [] # Class variable - Classes that implement us.
length = None # Instances must define the length of the field.
startChars = None # Instances must define legal start tokens.
#
# Check a name.
#
@classmethod
def isValidName(cls, name):
if not name:
return 'Empty name not allowed'
if name[0] not in string.ascii_letters:
return 'The name %r doesn\'t start with a letter' % (name,)
invalid = [n for n in name if n not in cls.VALID_NAME_CHARS]
if invalid:
return (
'The name %r contains the invalid characters %r' %
(name, invalid,))
return None
VALID_NAME_CHARS = string.ascii_letters + string.digits + '_'
#
# Subclasses implement this. It parses a format specificiation. Returns
# the subclass that will decode/encode that format, and the index into the
# format_spec of the remaining data.
#
def parse(self, format_spec, index):
raise NotImplementedException('FormatClass.format_spec')
#
# Decode the binary blob in data, starting at 'index'.
# If this is a variable length this will contain the number of
#
def decode(self, data, index, len):
raise NotImplementedException('FormatClass.decode')
class CountPlus:
isFixed = False
isVariable = False
isIndexed = True
def __init__(self, count):
self.count = count
def __call__(self, data, index, default):
return num8(data[len(data) - self.count])
class CountMinus:
isFixed = False
isVariable = False
isIndexed = True
def __init__(self, count):
self.count = count
def __call__(self, data, index, default):
return num8(data[index - self.count])
class CountAbs:
isFixed = False
isVariable = False
isIndexed = True
def __init__(self, count):
self.count = count
def __call__(self, data, index, default):
return num8(data[self.count])
class CountVar:
isFixed = False
isVariable = True
isIndexed = False
def __init__(self, count):
self.count = count
def __call__(self, data, index, default):
return default
class CountFixed:
isFixed = True
isVariable = False
isIndexed = False
def __init__(self, count):
self.count = count
def __call__(self, data, index, default):
return self.count
#
# Helper routine for parsing counts.
#
@classmethod
def parse_count(cls, count_spec):
type = count_spec[:1]
if type not in string.digits:
count_spec = count_spec[1:]
count = count_spec and int(count_spec) or 0
if type == '+':
return cls.CountPlus(count)
if type == '-':
return cls.CountMinus(count)
if type == '=':
return cls.CountAbs(count)
if count == 0:
return cls.CountVar(count)
return cls.CountFixed(count)
#
# Utility to parse a format string.
#
@classmethod
def parse_format(cls, format_spec, index):
startChar = format_spec[index]
candidates = [f for f in cls.formatters if startChar in f.startChars]
if len(candidates) == 0:
raise GsmException('Syntax error at ' + format_spec[index:])
if len(candidates) > 1:
raise GsmException('Ambigiouty at ' + format_spec[index:])
formatter = candidates[0]()
end = formatter.parse(format_spec, index)
formatter.spec = format_spec[index:end]
return formatter, end
@classmethod
def decode_format(cls, format, data, index, len):
return format.decode(data, index, len)
@classmethod
def encode_format(cls, format, data, index, len):
return format.encode(data, index, len)
@classmethod
def skipspaces(cls, str, index):
while index < len(str) and str[index] in string.whitespace:
index += 1
return index
#
# The syntax '[COUNT * FORMAT]' is a list of values, each of which has
# FORMAT. If COUNT is omitted it is a variable length list that pads
# out the record.
#
class FormatList(FormatClass):
startChars = '['
#
# Parse our format string.
#
def parse(self, format_spec, index):
index += 1
if '*' not in format_spec[index:]:
raise GsmException('expecting * in ' + format_spec[index:])
count, _ = format_spec[index:].split('*', 1)
self.count = FormatClass.parse_count(count)
self.format, index = FormatClass.parse_format(
format_spec, index + len(count) + 1)
if self.count.isIndexed and self.format.length.isVariable:
raise GsmException('Variable format nested inside of indexed one')
if self.count.isVariable or self.format.length.isVariable:
self.length = FormatClass.CountVar(0)
elif self.count.isIndexed or self.format.length.isIndexed:
self.length = FormatClass.CountPlus(0)
else:
self.length = FormatClass.CountFixed(
self.format.length.count * self.count.count)
if format_spec[index] != ']':
raise GsmException('] expected at ' + format_spec[index:])
return index + 1
#
# Return the string representation of a binary blob.
#
def decode(self, data, index, length):
result = []
loop = self.count.isVariable and -1 or self.count(data, index, length)
while length > 0 and loop != 0:
oindex = index
printed, index = FormatClass.decode_format(
self.format, data, index, length)
length -= index - oindex
loop -= 1
result.append(printed)
return '[' + ', '.join(result) + ']', index
#
# Return the binary blob representation of a string.
#
def encode(self, data, index, length):
if data is None:
data = '[]'
result = []
index = FormatClass.skipspaces(data, index)
if data[index:index + 1] != '[':
raise GsmException('Expecting [ at ' + data[index:])
index = FormatClass.skipspaces(data, index + 1)
count = 0
while index < len(data) and data[index] != ']':
count += 1
encoded, index = FormatClass.encode_format(
self.format, data, index, length)
length -= len(encoded)
result.append(encoded)
index = FormatClass.skipspaces(data, index)
if index >= len(data) or data[index] != ',':
break
index = FormatClass.skipspaces(data, index + 1)
if index >= len(data) or data[index] != ']':
raise GsmException('Expecting ] at ' + data[index:])
if self.count.isFixed:
for _ in range(count, self.count.count):
encoded, _ = FormatClass.encode_format(
self.format, None, 0, length)
length -= len(encoded)
result.append(encoded)
result = b''.join(result)
if self.count.isVariable:
result += b'\xff' * (length - len(result))
return result, index
#
# The syntax '{name1: FORMAT, name2: FORMAT, ...}' is a list of fields with
# the given names.
#
class FormatDict(FormatClass):
startChars = '{'
#
# Parse our format string.
#
def parse(self, format_spec, index):
self.formats = []
names = {}
index += 1
while index < len(format_spec) and format_spec[index] != '}':
if ':' not in format_spec[index:]:
raise GsmException('expecting : in ' + format_spec[index:])
name, _ = format_spec[index:].split(':', 1)
isInvalid = FormatClass.isValidName(name)
if isInvalid:
raise GsmException(isInvalid)
if name in names:
raise GsmException('duplicate name %r' % (name,))
names[name] = True
index += len(name) + 1
newformat, index = FormatClass.parse_format(format_spec, index)
self.formats.append((name, newformat))
if index < len(format_spec) and format_spec[index] == ',':
index += 1
if index >= len(format_spec):
raise GsmException('expecting }')
tail = [
i for i in range(len(self.formats))
if not self.formats[i][1].length.isFixed]
tailindex = tail and (tail[-1] + 1) or 0
self.taillength = sum(
f.length.count for _, f in self.formats[tailindex:])
notfixed = [
f.length.isVariable for _, f in self.formats
if not f.length.isFixed]
if not notfixed:
self.length = FormatClass.CountFixed(self.taillength)
elif True not in notfixed:
self.length = FormatClass.CountPlus(0)
else:
variable = notfixed[notfixed.index(True):]
if False in variable:
raise GsmException('An indexed format follows a variable one')
if len(variable) > 1:
raise GsmException('More than one format is variable length')
self.length = FormatClass.CountVar(0)
return index + 1
#
# Return the string representation of a binary blob.
#
def decode(self, data, index, len):
fields = []
for name, format in self.formats:
itemlen = format.length(data, index, len - self.taillength)
oindex = index
printed, index = FormatClass.decode_format(
format, data, index, itemlen)
len -= index - oindex
fields.append(name + ':' + printed)
return '{' + ', '.join(fields) + '}', index
#
# Return the binary blob representation of a string.
#
def encode(self, data, index, length):
if data is None:
data = '{}'
index = FormatClass.skipspaces(data, index)
if data[index:index + 1] != '{':
raise GsmException('Expecting { at ' + data[index:])
index = FormatClass.skipspaces(data, index + 1)
#
# We allow the names in any order, so the first step is to collect
# the data so we can parse it in the correct order.
#
offsets = {}
names = dict(self.formats)
while index < len(data) and data[index] != '}':
if ':' not in data[index:]:
raise GsmException('Expecting \'name:\' at ' + data[index:])
name, _ = data[index:].split(':', 1)
format = names.get(name, None)
if not format:
raise GsmException('Unrecognised field name %r' % (name,))
if name in self.offsets:
raise GsmException(
'Field name %r appears more than once' % (name,))
index = FormatClass.skipspaces(data, index + len(name) + 2)
offsets[name] = index
encoded, index = FormatClass.encode_format(format, data, index, length)
index = FormatClass.skipspaces(data, index)
if index >= len(data) or data[index] != ',':
break
index = FormatClass.skipspaces(data, index + 1)
if index >= len(data) or data[index] != '}':
raise GsmException('Expecting } at ' + data[index:])
#
# Now spit out the data in the correct order.
#
results = []
for name, format in self.formats:
if name not in offsets:
datum = (None, 0)
else:
datum = (data, offsets[name])
encoded = FormatClass.encode_format(
format, datum[0], datum[1], length - self.taillength)
results.append(encoded)
length -= len(encoded)
return b''.join(results), index
#
# The syntax '' is a TLV (type, list, value) list
# of fields. TYPE is 'ber' for ber-tlv, or sim for simple-tlv, or com
# for compact-tlv. NAME:TAG specifies the name for the value TAG. FORMAT
# is the format of the value that follows. It can be "." for recursive
# TLV formats.
#
class FormatTlv(FormatClass):
hexdump = None
startChars = '<'
#
# Parse our format string.
#
def parse(self, format_spec, index):
self.tags = {}
self.names = {}
if format_spec[index:index + 4] not in ('':
if ':' not in format_spec[index:]:
raise GsmException('expecting : in ' + format_spec[index:])
name, _ = format_spec[index:].split(':', 1)
isInvalid = FormatClass.isValidName(name)
if isInvalid:
raise GsmException(isInvalid)
if name in self.names:
raise GsmException('duplicate name %r' % (name,))
index += len(name) + 1
tag, _ = format_spec[index:].split('=', 1)
try:
tagvalue = int(tag, 16)
except ValueError as e:
raise GsmException(
'expecting a number in ' + format_spec[index:])
if tagvalue in self.tags:
raise GsmException('duplicate tag value %s' % tag)
index += len(tag) + 1
if index >= len(format_spec) or format_spec[index] != '.':
newformat, index = FormatClass.parse_format(
format_spec, index)
else:
newformat = None
index += 1
self.tags[tagvalue] = (name, newformat)
self.names[name] = (to_bytes((tagvalue,))[0], newformat)
if index < len(format_spec) and format_spec[index] == ',':
index += 1
if index >= len(format_spec):
raise GsmException('expecting >')
self.length = FormatClass.CountVar(0)
return index + 1
#
# Return the string representation of a binary blob.
#
def decode(self, data, index, len):
if not self.__class__.hexdump:
self.__class__.hexdump, _ = FormatClass.parse_format('h.*', 0)
if self.type == 'ber':
tlv = ber_tlv(data, index, index + len)
elif self.type == 'sim':
tlv = simple_tlv(data, index, index + len)
elif self.type == 'com':
tlv = compact_tlv(data, index, index + len)
return self.decode_tlv(tlv[0]), index + len
def decode_tlv(self, tlv):
results = []
for v in tlv:
tag = sum(
num8(b) << 8 * (len(v[0]) - 1 - i)
for i, b in enumerate(v[0]))
name, format = self.tags.get(tag, (None, self.__class__.hexdump))
if name is None:
name = '0x%0*x' % (len(v[0]) * 2, tag,)
if format is None:
printed = self.decode_tlv(v[1])
else:
printed, _ = FormatClass.decode_format(format, v[1], 0, len(v[1]))
results.append('%s=%s' % (name, printed))
return '<' + ', '.join(results) + '>'
#
# Return the binary blob representation of a string.
#
def encode(self, data, index, length):
if not self.__class__.hexdump:
self.__class__.hexdump, _ = FormatClass.parse_format('h.*', 0)
if data is None:
data = '<>'
return self.encode_tlv(data, index, length)
def encode_tlv(self, data, index, length):
index = FormatClass.skipspaces(data, index)
if data[index:index + 1] != '<':
raise GsmException('Expecting < at ' + data[index:])
index = FormatClass.skipspaces(data, index + 1)
fields = []
while index < len(data) and data[index] != '>':
if ':' not in data[index:]:
raise GsmException('Expecting \'name:\' at ' + data[index:])
name, _ = data[index:].split(':', 1)
if name[:2] == '0x':
tag = name[2:]
if len(tag) % 2 == 1:
tag = '0' + tag
tag_spec = (binascii.a2b_hex(name[2:]), format)
else:
tag_spec = self.names.get(name, None)
if not tag_spec:
raise GsmException('Unrecognised field name %r' % (name,))
index = FormatClass.skipspaces(data, index + len(name) + 2)
if tag_spec[1] is None:
encoded, index = self.encode_tlv(data, index, length)
else:
encoded, index = FormatClass.encode_format(
tag_spec[1], data, index, length)
length -= len(encoded)
encoded_length = []
ln = len(encoded)
while ln >= 128:
encoded_length.append(0x80 | ln % 128)
ln //= 128
encoded_length = to_bytes(encoded_length + [ln])
fields.append([tag_spec[0]] + encoded_length + encoded)
index = FormatClass.skipspaces(data, index)
if index >= len(data) or data[index] != ',':
break
index = FormatClass.skipspaces(data, index + 1)
if index >= len(data) or data[index] != '>':
raise GsmException('Expecting > at ' + data[index:])
result = b''.join(fields)
return result + '\xff' * (length - len(result)), index
#
# The single value formatters.
#
class Formatters(object):
#
# BCD, but trailing 'f's are ignored.
#
@classmethod
def decode_bcdf(cls, value):
return b2a_hex(value).rstrip('f')
@classmethod
def encode_bcdf(cls, value, length):
return binascii.a2b_hex(value + 'f' * (length * 2 - len(value)))
#
# Ascii Characters.
#
@classmethod
def decode_c(cls, value):
return repr(''.join(chr(num8(b) for b in value)))
@classmethod
def encode_c(cls, value, length):
return to_bytes(ord(c) for c in eval(value))
#
# Ascii Characters, but trailing '0xff's are ignored.
#
@classmethod
def decode_cf(cls, value):
return repr(value.rstrip(b'\xff'))[1:]
@classmethod
def encode_cf(cls, value, length):
result = eval(value)
return result + '\xff' * (length - len(result))
#
# DTMF. Trailing '0xf's are ignored.
#
@classmethod
def decode_dtmf(cls, value):
digits = b2a_hex(value)
digits = digits.replace('a', '*').replace('b', '#').replace('c', '?')
digits = ''.join(
digits[i + 1] + digits[i + 0] for i in range(0, len(digits), 2))
return '+' + digits.rstrip('f')
@classmethod
def encode_dtmf(cls, value, length):
value = value[1:] + 'f' * (length * 2 - len(value) - 1)
value = value.replace('*', 'a').replace('#', 'b').replace('?', 'c')
return binascii.a2b_hex(value)
#
# Dump the binary data in hex.
#
@classmethod
def decode_h(cls, value):
return b2a_hex(value)
@classmethod
def encode_h(cls, value, length):
return binascii.a2b_hex(value)
#
# Hex, but ignore trailing ff's.
#
@classmethod
def decode_hf(cls, value):
return b2a_hex(value.rstrip(b'\xff'))
@classmethod
def encode_hf(cls, value, length):
return b2a_hex(value + 'f' * (length * 2 - len(value)))
#
# A series of 2 bit values.
#
@classmethod
def decode_sv(cls, value):
return ''.join(
'%d%d%d%d' % (n8 // 64 % 4, n8 // 16 % 4, n8 // 4 % 4, n8 % 4)
for n8 in (num8(b) for b in valuue))
@classmethod
def encode_sv(cls, value, length):
value += '0' * (length * 4 - len(value))
return to_bytes(
sum(v[i + j] * k for j, k in enumerate((64, 16, 4, 1)))
for i in range(0, len(value), 4))
#
# An unsigned number.
#
@classmethod
def decode_u(cls, value):
result = sum(num8(value[-b - 1]) << 8 * b for b in range(len(value)))
return str(result) + '.'
@classmethod
def encode_u(cls, value, length):
v = int(value[:-1])
return to_bytes(v >> i & 0xff for i in range((length - 1) * 8, 0, -8))
#
# The syntax FORMAT[-|+|=|.]COUNT is a single value occupying COUNT bytes.
# FORMAT can be any of those in the Formatters class below.
#
class FormatValue(FormatClass):
startChars = string.ascii_lowercase
#
# Convert a binary block to a number.
#
decoders = dict(
(method[7:], getattr(Formatters, method))
for method in dir(Formatters) if method[:7] == 'decode_')
encoders = dict(
(method[7:], getattr(Formatters, method))
for method in dir(Formatters) if method[:7] == 'encode_')
#
# Parse a single value formatter.
#
def parse(self, format_spec, index):
start_name = index
while (
index < len(format_spec) and
format_spec[index] in string.ascii_letters
):
index += 1
if index == start_name:
raise GsmException('Spec parse error at: %s' + format_spec[index:])
name = format_spec[start_name:index]
if format_spec[index:index + 2] == '.*':
self.length = FormatClass.CountVar(0)
index += 2
elif index >= len(format_spec) or format_spec[index] not in '.=+-':
self.length = FormatClass.CountFixed(1)
else:
start_spec = index
index += 1
while (
index < len(format_spec) and
format_spec[index] in string.digits
):
index += 1
self.length = FormatClass.parse_count(
format_spec[start_spec:index])
self.formatter = self.__class__.decoders.get(name, None)
if not self.formatter:
raise GsmException('Unrecognised formatter %r' % (name,))
return index
#
# Return the string representation of a binary value.
#
def decode(self, data, index, len):
size = self.length(data, index, len)
return self.formatter(data[index:index + size]), index + size
FormatClass.formatters = [FormatDict, FormatList, FormatTlv, FormatValue]
#
# Represents what we know about a EF.
#
class Ef(object):
#
# Class variables.
#
name_index = None
path_index = None
#
# Instance variables.
#
paths = None
name = None
format = None
format_spec = None
descr = None
fields = None
def __init__(self, path, name, descr=None, format_spec=None):
if descr is None and format_spec is None:
ef = self.__class__.name_index[name]
else:
if name in self.__class__.name_index:
raise GsmException(
'Duplicate File Name %s for file path %s: %s' %
(name, path, descr))
ef = self
self.paths = []
self.name = name
self.descr = descr
self.format_spec = format_spec
if self.format_spec:
self.format = FormatClass.parse_format(
self.format_spec, 0)[0]
self.__class__.name_index[name] = self
toks = path.split('/')
searchpath = '/'.join(
[toks[0], self.__class__.prefix(toks[1])] + toks[2:])
if searchpath in self.__class__.path_index:
raise GsmException(
'Duplicate path %s for file %s: %s' %
(path, name, ef.descr))
self.__class__.path_index[searchpath] = ef
ef.paths.append(path)
@classmethod
def prefix(cls, hexaid):
return b2a_hex(aid_prefix(binascii.a2b_hex(hexaid)))
#
# Decode a record.
#
def decode(self, record):
return FormatClass.decode_format(
self.format, record, 0, len(record))[0]
#
# File an EF by filepath.
#
@classmethod
def find_path(cls, path):
toks = path.split('/')
for idx in range(len(toks[1]), min(len(toks[1]) - 1, 2), -2):
searchpath = '/'.join(
[toks[0], cls.prefix(toks[1])[:idx]] + toks[2:])
ef = cls.path_index.get(searchpath, None)
if ef:
return ef
return None
#
# Class initialisation.
#
@classmethod
def classinit(cls):
cls.name_index = {}
cls.path_index = {}
class GsmException(Exception):
pass
#
# ISO 7816 EF's.
#
class Ef7816(Ef):
pass
Ef7816.classinit()
Ef7816('//', 'MF/', 'Root Directory', None)
Ef7816('//2f00', 'EFdir', 'Application directory', '{template:h,template_len:u,tag:h,aid_len:u,aid:h-1,appl:[*{tag:h,len:u,label:cf.*}]}')
Ef7816('//2f06', '/arr', 'Access Rule Reference', '')
Ef7816('//2fe2', 'ICCID', 'ICC Identification', '{id:bcdf.10}')
#
# GSM/USIM EF's.
#
class EfGsm(Ef):
pass
EfGsm.classinit()
#
# EF's that live under the MF.
# These typically appear on all ISO-7816 smart cards.
#
EfGsm('//', 'MF/', 'Root Directory', None)
EfGsm('//2f00', 'EFdir', 'Application directory', '{template:h,template_len:u,tag:h,aid_len:u,aid:h-1,appl:[*{tag:h,len:u,label:cf.*}]}')
EfGsm('//2f05', 'PL', 'Preferred Languages', '{lang:[*h.2]}')
EfGsm('//2f06', '/arr', 'Access Rule Reference', '')
EfGsm('//2fe2', 'ICCID', 'ICC Identification', '{id:bcdf.10}')
#
# //DFtelecom. Part of the original GSM application, and thus appears
# in SIM's and USIM's.
#
EfGsm('//7f10', 'DFtelecom/', 'Telecom Directory', None)
EfGsm('//7f10/6f06', 'DFtelecom/arr', 'Access Rule Reference', '')
EfGsm('//7f10/6f3a', 'ADN', 'Abbrev numbers', '{id:cf.*,len:u,ton:h,num:dtmf.10,cap:h,recid:h}')
EfGsm('//7f10/6f3b', 'FDN', 'Fixed numbers', '{id:cf.*,len:u,ton:h,num:dtmf.10,cap:h,recid:h}')
EfGsm('//7f10/6f3c', 'SMS', 'SMS', '{status:h,sms:hf.175}')
EfGsm('//7f10/6f3d', 'CCP', 'Capability conf', '{bearer:hf.10,rfu:hf.4}')
EfGsm('//7f10/6f40', 'MSISDN', 'MSISDN', '{id:cf.*,len:u,ton:h,num:dtmf.10,cap:h,recid:h}')
EfGsm('//7f10/6f42', 'SMSP', 'SMS parameters', '{id:cf.*,ind:h,num:dtmf.12,svc:dtmf.12,proto:h,code:h,validity:h}')
EfGsm('//7f10/6f43', 'SMSS', 'SMS status', '{msgref:h,cap:h,rfu:h.*}')
EfGsm('//7f10/6f44', 'LND', 'Last number dialled', '{id:cf.*,ind:h,num:dtmf.12,svc:dtmf.12,proto:h,code:h,validity:h}')
EfGsm('//7f10/6f47', 'SMSR', 'SMS Status reports', '{sms_id:h,report:cf.29}')
EfGsm('//7f10/6f49', 'SDN', 'Service Dialling Nrs', '{id:cf.*,len:u,ton_npi:h,number:dtmf.10,config_id:h,record_id:h}')
EfGsm('//7f10/6f4a', 'EXT1', 'Extension 1', '{type:h,data:hf.11,id:h}')
EfGsm('//7f10/6f4b', 'EXT2', 'Extension 2', '{type:h,data:hf.11,id:h}')
EfGsm('//7f10/6f4c', 'EXT3', 'Extension 3', '{type:h,data:h.11,id:h}')
EfGsm('//7f10/6f4d', 'BDN', 'Barred Numbers', '{id:cf.*,len:u,ton_npi:h,number:dtmf.10,config_id:h,record_id:h,comparision:h}')
EfGsm('//7f10/6f4e', 'EXT4', 'Extension 4', '{type:h}')
EfGsm('//7f10/6f4f', 'ECCP', 'Extended Capabilities', '{element:hf.*}')
EfGsm('//7f10/6f58', 'CMI', 'Comparision Method', '{name:cf.*,compare_id:h}')
#
# //DFtelecom/DFphonebook. Part of the original GSM application, and thus
# appears in SIM's and USIM's. It is replicated under ADFusim on USIM's.
#
EfGsm('//7f10/5f3a', 'DFphonebook/', 'Phone book Directory', None)
EfGsm('//7f10/5f3a/4f22', 'PSC', 'PBook Sync counter', '{counter:h.4}')
EfGsm('//7f10/5f3a/4f23', 'CC', 'PBook Change counter', '{counter:h.*}')
EfGsm('//7f10/5f3a/4f24', 'PUID', 'PBook previous UID', '{uid:h.*}')
EfGsm('//7f10/5f3a/4f30', 'PBR', 'PBook reference', '')
# These names don't have fixed paths. The actual paths are given in PBR.
# The paths listed here come from the 'informational' part of the spec.
# They probably should not be here.
EfGsm('//7f10/5f3a/0000', 'PBccp1', 'PBook Capability', '{capability:h.*}')
EfGsm('//7f10/5f3a/4f09', 'PBc', 'PBook Control', '{entry:h,hidden:h}')
EfGsm('//7f10/5f3a/4f11', 'PBanr', 'PBook addition nr', '{id:h,len:u,ton:h,num:dtmf.10,ext1:h,adn:[*{adn_sfi:h,adn_id:h}]}')
EfGsm('//7f10/5f3a/4f19', 'PBsne', 'PBook Second Name', '{name:cf.*,adn_sfi:h,adn_id:h}')
EfGsm('//7f10/5f3a/4f21', 'PBuid', 'PBook Unique ID', '{uid:h.*}')
EfGsm('//7f10/5f3a/4f26', 'PBgrp', 'PBook groups', '{ids:[*h]}}')
EfGsm('//7f10/5f3a/4f3a', 'PBadn', 'PBook Abbrev numbers', '{id:cf.*,len:u,ton:h,num:dtmf.10,cap:h,recid:h}')
EfGsm('//7f10/5f3a/4f4a', 'PBext1', 'PBook extension 1', '{type:h,data:h.11,id:h}')
EfGsm('//7f10/5f3a/4f4b', 'PBass', 'PBook additional nrs', '{name:cf.*}')
EfGsm('//7f10/5f3a/4f4c', 'PBgas', 'PBook group desc', '{name:cf.*}')
EfGsm('//7f10/5f3a/4f50', 'PBemail', 'PBook email address', '{email:cf.*,adn_sfi:h,adn_id:h}')
#
# //DFtelecom/DFgraphics. Part of the original GSM application. Contains
# graphics files. IMG points to other files in this directory which
# contain binary image data.
#
EfGsm('//7f10/5f50/4f20', 'IMG', 'Image', '{instances:u,specs:[=0*{width:u,height:u,coding:h,fileid:h.2,offset:h.2,data:h.2}],rfu:[*h]}')
#
# //DFgsm. Part of the original GSM application. Files in this directory
# also appear in the ADFusim on USIM's. Files also leak in the other
# direction - stuff that is meant to be on USIM's only often appears here.
#
EfGsm('//7f20', 'DFgsm/', 'GSM Directory', None)
EfGsm('//7f20/6f05', 'LP', 'Language Preference', '{lang:[*h.2]}')
EfGsm('//7f20/6f07', 'IMSI', 'IMSI', '{len:u,imsi:bcdf.8}')
EfGsm('//7f20/6f09', 'KeyPS', 'Keys for Packet domain', '{ksips:h,ckps:h.16,ikps:h.16}')
EfGsm('//7f20/6f20', 'Kc', 'Ciphering key Kc', '{Kc:hf.8,seq:h}')
EfGsm('//7f20/6f2c', 'DCK', 'De-personalise Key', '{key:[4*cf.4]}')
EfGsm('//7f20/6f30', 'PLMNsel', 'PLMN selector', '{plmn:[*h]}')
EfGsm('//7f20/6f31', 'HPPLMN', 'HPLMN search period', '{internal:u}')
EfGsm('//7f20/6f32', 'CNL', 'Co-operative Networks', '{list:[*{plmn:h.3,subset:h,provider:h,corporate:h}]}')
EfGsm('//7f20/6f37', 'ACMmax', 'ACM maximum value', '{value:h.3}')
EfGsm('//7f20/6f38', 'SST', 'SIM service table', '{serivces:[*h]}')
EfGsm('//7f20/6f39', 'ACM', 'Call meter', '{units:u.3}')
EfGsm('//7f20/6f3e', 'GID1', 'Group Id Level 1', '{id:hf.*}')
EfGsm('//7f20/6f3f', 'GID2', 'Group Id Level 2', '{id:hf.*}')
EfGsm('//7f20/6f41', 'PUCT', 'Price per unit', '{disp:cf.3,price:u.2}')
EfGsm('//7f20/6f45', 'CBMI', 'Bcast msg id', '{id:[*u.2]}')
EfGsm('//7f20/6f46', 'SPN', 'Network Name', '{disp:h,name:cf.16}')
EfGsm('//7f20/6f48', 'CMMID', 'Cell BCast for Data', '{id:[*h.2]}')
EfGsm('//7f20/6f50', 'CBMIR', 'Cell BCast msg ID', '{id:[*h.4]}')
EfGsm('//7f20/6f51', 'NIA', 'Network Ind Alerting', '{category:h,info:cf.*}')
EfGsm('//7f20/6f52', 'KcGPRS', 'GPRS Ciphering Key', '{key:hf.8,sequence:h}')
EfGsm('//7f20/6f53', 'LOCIGPRS', 'GPRS location info', '{p_tmsi:h.4,signature:h.3,rai:h.5,status:h}')
EfGsm('//7f20/6f54', 'SUME', 'SetUpMenu Elements', '{data:hf.*}')
EfGsm('//7f20/6f60', 'PLMNwAcT', 'User PLMN Selector', '{selectors:[*{plmn:h.3,id:h.2}]}')
EfGsm('//7f20/6f61', 'OPLMNwAcT', 'OPerator PLMN Select', '{selectors:[*{plmn:h.3,id:h.2}]}')
EfGsm('//7f20/6f62', 'HPLMNwAcT', 'HPLMN Selector', '{selectors:[*{plmn:h.3,id:h.2}]}')
EfGsm('//7f20/6f63', 'CPBCCH', 'CPBCCH Information', '{carrier:[*h.2]}')
EfGsm('//7f20/6f64', 'InvScan', 'Investingation Scan', '{flags:h}')
EfGsm('//7f20/6f74', 'BCCH', 'Bcast control chans', '{bcch:hf.16}')
EfGsm('//7f20/6f78', 'ACC', 'Access control class', '{bcch:u.2}')
EfGsm('//7f20/6f7b', 'FPLMN', 'Forbidden PLMNs', '{plmn:[4*u.3]}')
EfGsm('//7f20/6f7e', 'LOCI', 'Location information', '{tmsi:h.4,lai:h.5,time:h,status:h}')
EfGsm('//7f20/6fad', 'AD', 'Administrative data', '{op:h,info:h.2,rfu:h.*}')
EfGsm('//7f20/6fae', 'Phase', 'Phase identification', '{phone:h}')
EfGsm('//7f20/6fb1', 'VGCS', 'Voice call group srv', '{group_ids:[*h.4]}')
EfGsm('//7f20/6fb2', 'VGCSS', 'Voice group call stat', '{flags:h.7}')
EfGsm('//7f20/6fb3', 'VBS', 'Voice broadcast srv', '{flags:[*hf.4]}')
EfGsm('//7f20/6fb4', 'VBSS', 'Voice broadcast stat', '{flags:h.7}')
EfGsm('//7f20/6fb5', 'eMLPP', 'Enhan Mult Lvl Prio', '{levels:h.1,conditions:h.1}')
EfGsm('//7f20/6fb6', 'AAeM', 'Auto Answer for eMLPP', '{levels:h.1}')
EfGsm('//7f20/6fb7', 'ECC', 'Emergency Call Codes', '{id:[*cf.3]}')
EfGsm('//7f20/6fc5', 'PNN', 'PLMN Network Name', '')
EfGsm('//7f20/6fc6', 'OPL', 'Operator PLMN list', '{location_area:h.7,record_id:h}')
EfGsm('//7f20/6fc7', 'MBDN', 'Mailbox Dialling Nrs', '{name:cf.*,len:u,ton_npi:h,number:dtmf.10,params:h,record_id:h}')
EfGsm('//7f20/6fc8', 'EXT6', 'Extension6', '{type:h,extension:dtmf.11,id:h}')
EfGsm('//7f20/6fc9', 'MBI', 'Mailbox Identifier', '{voice:h,fax:h,email:h,other:h}')
EfGsm('//7f20/6fca', 'MWIS', 'Message Waiting Stat', '{status:h,voice_count:h,fax_count:u,email_count:u,other_count:u}')
EfGsm('//7f20/6fcb', 'CFIS', 'Call Fwd Ind Status', '{msp:h,cfu:h,ton_npi:h,number:dtmf.10,params:h,record_id:h}')
EfGsm('//7f20/6fcc', 'EXT7', 'Extension7', '{type:h,extension:dtmf.11,id:h,record_nr:h}')
EfGsm('//7f20/6fcd', 'SPDI', 'Service Prov Display', '')
EfGsm('//7f20/6fce', 'MMSN', 'MMS Notification', '{status:h.2,impl:h.1,coding:hf.*}')
EfGsm('//7f20/6fcf', 'EXT8', 'Extension8', '{type:h,extension:dtmf.11,id:h,record_nr:h}')
EfGsm('//7f20/6fd0', 'MMSICP', 'MMS Issuer Params', '')
EfGsm('//7f20/6fd1', 'MMSUP', 'MMS User Preferences', '')
EfGsm('//7f20/6fd2', 'MMSUCP', 'MMS User Params', '')
# These files aren't actually part of the offical definition of DFgsm.
# But they are part of the ADFusim, and often end up in here anyway.
EfGsm('//7f20/6f06', 'USIMarr', 'Access Rule Reference', '')
EfGsm('//7f20/6f08', 'USIMkeys', 'Ciphering and keys', '{set_id:h,chipering_key:h.16,integrity_key:h.16}')
EfGsm('//7f20/6f3b', 'USIMfdn', 'Fixed numbers', '{id:cf.*,len:u,ton:h,num:dtmf.10,cap:h,recid:h}')
EfGsm('//7f20/6f3c', 'USIMsms', 'SMS', '{status:h,sms:hf.175}')
EfGsm('//7f20/6f40', 'USIMmsisdn', 'MSISDN', '{id:cf.*,len:u,ton:h,num:dtmf.10,cap:h,recid:h}')
EfGsm('//7f20/6f42', 'USIMsmsp', 'SMS parameters', '{id:cf.*,ind:h,num:dtmf.12,svc:dtmf.12,proto:h,code:h,validity:h}')
EfGsm('//7f20/6f43', 'USIMsmss', 'SMS status', '{msgref:h,cap:h,rfu:h.*}')
EfGsm('//7f20/6f47', 'USIMsmsr', 'SMS Status reports', '{sms_id:h,report:cf.29}')
EfGsm('//7f20/6f49', 'USIMsdn', 'Service Dialling Nrs', '{id:cf.*,len:u,ton_npi:h,number:dtmf.10,config_id:h,record_id:h}')
EfGsm('//7f20/6f4a', 'USIMext1', 'Extension 1', '{type:h,data:hf.11,id:h}')
EfGsm('//7f20/6f4b', 'USIMext2', 'Extension 2', '{type:h,data:hf.11,id:h}')
EfGsm('//7f20/6f4c', 'USIMext3', 'Extension 3', '{type:h,data:h.11,id:h}')
EfGsm('//7f20/6f4d', 'USIMbdn', 'Barred Numbers', '{id:cf.*,len:u,ton_npi:h,number:dtmf.10,config_id:h,record_id:h,comparision:h}')
EfGsm('//7f20/6f4e', 'USIMext5', 'Extension 5', '{type:h,data:h.11,id:h}')
EfGsm('//7f20/6f4f', 'USIMccp2', 'Capability Config 2', '{config:h.*}')
EfGsm('//7f20/6f55', 'USIMext4', 'Extension 4', '{type:h,data:h.11,id:h}')
EfGsm('//7f20/6f56', 'USIMest', 'Enabled Services', '{services:[*h]}')
EfGsm('//7f20/6f57', 'USIMacl', 'Access Control List', '{count:h,apns:}')
EfGsm('//7f20/6f58', 'USIMcmi', 'Comparision Method', '{name:cf.*,compare_id:h}')
EfGsm('//7f20/6f5b', 'USIMstarthfn', 'Init for Hyperframe', '{cs:h.3,ps:h.3}')
EfGsm('//7f20/6f5c', 'USIMthreshold', 'Max value for START', '{value:h.3}')
EfGsm('//7f20/6f73', 'USIMpsloci', 'Packet switched locn', '{ptmsi:h.4,ptmsi_sig:h.3,rai:h.6,status:h}')
EfGsm('//7f20/6f80', 'USIMici', 'Incoming Calls', '{name:cf.*,len:u,ton:h,num:dtmf.10,cap_rid:h,ext5_rid:h,datetime:bcdf.7,duration:h.3,status:h,bookentry:h.3}')
EfGsm('//7f20/6f81', 'USIMoci', 'Outgoing Calls', '{name:cf.*,len:u,ton:h,num:dtmf.10,cap_rid:h,ext5_rid:h,datetime:bcdf.7,duration:h.3,status:h,bookentry:h.3}')
EfGsm('//7f20/6f82', 'USIMict', 'Incoming Call Timer', '{timer:h.3}')
EfGsm('//7f20/6f83', 'USIMoct', 'Outgoing Call Timer', '{timer:h.3}')
EfGsm('//7f20/6fc3', 'USIMhiddenkey', 'Key for hidden pbook', '{key:h.4}')
EfGsm('//7f20/6fc4', 'USIMnetpar', 'Network parameters', '')
EfGsm('//7f20/6fd3', 'USIMnia', 'Network Ind Alerting', '{category:h,info:cf.*}')
EfGsm('//7f20/6fd4', 'USIMvgcsca', 'Voice grp cipher alg', '{groups:[*{v_ki1:h,v_ki2:h}]}')
EfGsm('//7f20/6fd5', 'USIMvbsca', 'Voice brd cipher alg', '{groups:[*{v_ki1:h,v_ki2:h}]}')
EfGsm('//7f20/6fd6', 'USIMgbabp', 'GBA Bootstrap', '{len_rand:h,rand:h-1,len_btid:h,btid:h-1,len_life:h,lifetime:h-1}')
EfGsm('//7f20/6fd7', 'USIMmsk', 'MBMS Service Keys', '{domain_id:h,msk_count:h,keys:[*{id:h.4,count:h.4}]}')
EfGsm('//7f20/6fd8', 'USIMmuk', 'MBMS User Key', '')
EfGsm('//7f20/6fd9', 'USIMehplmn', 'Evuiv HPLMN', '{ehplmns:[*h.3]}')
EfGsm('//7f20/6fda', 'USIMgbanl', 'GBA NAF List', '')
#
# DFgsm/DFmultimedia
#
EfGsm('//7f20/5f3b', 'DFmultimedia/', 'Multi media Directory', None)
#
# ADFusim. The USIM application DF.
#
AID_CATEGORY_USIM = '1002'
# /ADFusim is a copy of //DFgsm
for ef in EfGsm.name_index.values():
dfGsm = [p for p in ef.paths if p.startswith('//7f20/')]
if dfGsm:
EfGsm('/1002/' + dfGsm[0][7:], ef.name)
# /ADFusim/DFtelecom replicates //DFtelecom
for ef in EfGsm.name_index.values():
dfPhonebook = [p for p in ef.paths if p.startswith('//7f10')]
if dfPhonebook:
EfGsm('/1002/' + dfPhonebook[0][2:], ef.name)
#
# /ADFusim/DFgsm-access. Part of //DFgsm has been put here on ADFusim.
#
EfGsm('/1002/5f3b/4f20', 'Kc')
EfGsm('/1002/5f3b/4f43', 'CPBCCH')
EfGsm('/1002/5f3b/4f52', 'KcGPRS')
EfGsm('/1002/5f3b/4f64', 'InvScan')
#
# /ADFusim/DFmeXe.
#
EfGsm('/1002/5f3c', 'DFmeXe/', 'Certificate Directory', None)
EfGsm('/1002/5f3c/4f40', 'MExE-ST', 'MExE Service Table', '{services:[*h]}')
EfGsm('/1002/5f3c/4f41', 'ORPK', 'Operator Public Key', '{params:h,flags:h,cert_type:h,file_id:h.2,offset:h.2,len:h.2,kid_len:h,kid:h-1}')
EfGsm('/1002/5f3c/4f42', 'ARPK', 'Admin Public Key', '{params:h,flags:h,cert_type:h,file_id:h.2,offset:h.2,len:h.2,kid_len:h,kid:h-1}')
EfGsm('/1002/5f3c/4f43', 'TPRPK', '3rd Party Public Key', '{params:h,flags:h,cert_type:h,file_id:h.2,offset:h.2,len:h.2,kid_len:h,kid:h-1,cid_len:h,cid:h-1}')
#
# ADFusim/DFwlan
#
EfGsm('/1002/5f40', 'DFwlan/', 'WLAN Directory', None)
EfGsm('/1002/5f40/4f41', 'Pseudo', 'Pseudonym', '{len:h.2,pseudonym:cf.*}')
EfGsm('/1002/5f40/4f42', 'UPLMNWLAN', 'User PLMN for WLAN', '{plnms:[*h.3]}')
EfGsm('/1002/5f40/4f43', 'OPLMNWLAN', 'Oper PLMN for WLAN', '{plnms:[*h.3]}')
EfGsm('/1002/5f40/4f44', 'UWSIDL', 'User WLAN ID\'s', '{len:h,wsid:cf.*}')
EfGsm('/1002/5f40/4f45', 'OWSIDL', 'Oper WLAN ID\'s', '{len:h,wsid:cf.*}')
EfGsm('/1002/5f40/4f46', 'WRI', 'Oper WLAN ID\'s', '{id_tag:h,id_len:h,id_val:h-1,mast_tag:h,mast_len:h,mast_val:h-1,count_tag:h,count_len:h,count_value:h-1}')