Import slightly modified version of mitshell/card
the modifications can be found in https://github.com/mitshell/card/pull/2 and I wouuld hope that once Benoit has merged this we can remove the local copy here and simply rely on pypi pulling it from the repo.
This commit is contained in:
parent
5666cd6818
commit
4086758393
|
@ -0,0 +1,455 @@
|
||||||
|
# -*- coding: UTF-8 -*-
|
||||||
|
"""
|
||||||
|
card: Library adapted to request (U)SIM cards and other types of telco cards.
|
||||||
|
Copyright (C) 2010 Benoit Michau
|
||||||
|
|
||||||
|
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, write to the Free Software Foundation, Inc.,
|
||||||
|
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||||
|
"""
|
||||||
|
|
||||||
|
#################################
|
||||||
|
# 3GPP SIM and USIM File-System #
|
||||||
|
# see TS 51.11 for SIM #
|
||||||
|
# see TS 31.102 for USIM #
|
||||||
|
#################################
|
||||||
|
|
||||||
|
# (U)SIM file-system dictionnaries
|
||||||
|
# (absolut_file_address) : 'file_name'
|
||||||
|
|
||||||
|
SIM_FS = {
|
||||||
|
(0x3F, 0x00) : 'MF',
|
||||||
|
(0x2F, 0xE2) : 'EF_ICCID',
|
||||||
|
(0x2F, 0x05) : 'EF_PL',
|
||||||
|
(0x7F, 0x23) : 'DF_FP-CTS',
|
||||||
|
(0x7F, 0x22) : 'DF_IS-41',
|
||||||
|
(0x7F, 0x10) : 'DF_TELECOM',
|
||||||
|
(0x7F, 0x10, 0x6F, 0x3A) : 'EF_ADN',
|
||||||
|
(0x7F, 0x10, 0x6F, 0x3B) : 'EF_FDN',
|
||||||
|
(0x7F, 0x10, 0x6F, 0x3C) : 'EF_SMS',
|
||||||
|
(0x7F, 0x10, 0x6F, 0x3D) : 'EF_CCP',
|
||||||
|
(0x7F, 0x10, 0x6F, 0x40) : 'EF_MSISDN',
|
||||||
|
(0x7F, 0x10, 0x6F, 0x42) : 'EF_SMSP',
|
||||||
|
(0x7F, 0x10, 0x6F, 0x43) : 'EF_SMSS',
|
||||||
|
(0x7F, 0x10, 0x6F, 0x44) : 'EF_LND',
|
||||||
|
(0x7F, 0x10, 0x6F, 0x47) : 'EF_SMSR',
|
||||||
|
(0x7F, 0x10, 0x6F, 0x49) : 'EF_SDN',
|
||||||
|
(0x7F, 0x10, 0x6F, 0x4A) : 'EF_EXT1',
|
||||||
|
(0x7F, 0x10, 0x6F, 0x4B) : 'EF_EXT2',
|
||||||
|
(0x7F, 0x10, 0x6F, 0x4C) : 'EF_EXT3',
|
||||||
|
(0x7F, 0x10, 0x6F, 0x4D) : 'EF_BDN',
|
||||||
|
(0x7F, 0x10, 0x6F, 0x4E) : 'EF_EXT4',
|
||||||
|
(0x7F, 0x10, 0x6F, 0x58) : 'EF_CMI',
|
||||||
|
(0x7F, 0x10, 0x6F, 0x4F) : 'EF_ECCP',
|
||||||
|
(0x7F, 0x10, 0x5F, 0x50) : 'DF_GRAPHICS',
|
||||||
|
(0x7F, 0x10, 0x5F, 0x50, 0x4F, 0x20) : 'EF_IMG',
|
||||||
|
(0x7F, 0x20) : 'DF_GSM',
|
||||||
|
(0x7F, 0x20, 0x5F, 0x3C) : 'DF_MExE',
|
||||||
|
(0x7F, 0x20, 0x5F, 0x3C, 0x4F, 0x40) : 'EF_MExE-ST',
|
||||||
|
(0x7F, 0x20, 0x5F, 0x3C, 0x4F, 0x41) : 'EF_ORPK',
|
||||||
|
(0x7F, 0x20, 0x5F, 0x3C, 0x4F, 0x42) : 'EF_ARPK',
|
||||||
|
(0x7F, 0x20, 0x5F, 0x3C, 0x4F, 0x43) : 'EF_TPRPK',
|
||||||
|
(0x7F, 0x20, 0x5F, 0x30) : 'DF_IRIDIUM',
|
||||||
|
(0x7F, 0x20, 0x5F, 0x31) : 'DF_GLOBALSTAR',
|
||||||
|
(0x7F, 0x20, 0x5F, 0x32) : 'DF_ICO',
|
||||||
|
(0x7F, 0x20, 0x5F, 0x33) : 'DF_ACeS',
|
||||||
|
(0x7F, 0x20, 0x5F, 0x40) : 'DF_EIA/TIA-553',
|
||||||
|
(0x7F, 0x20, 0x5F, 0x60) : 'DF_CTS',
|
||||||
|
(0x7F, 0x20, 0x5F, 0x70) : 'DF_SoLSA',
|
||||||
|
(0x7F, 0x20, 0x5F, 0x70, 0x4F, 0x30) : 'EF_SAI',
|
||||||
|
(0x7F, 0x20, 0x5F, 0x70, 0x4F, 0x31) : 'EF_SLL',
|
||||||
|
(0x7F, 0x20, 0x6F, 0x05) : 'EF_LP',
|
||||||
|
(0x7F, 0x20, 0x6F, 0x07) : 'EF_IMSI',
|
||||||
|
(0x7F, 0x20, 0x6F, 0x20) : 'EF_Kc',
|
||||||
|
(0x7F, 0x20, 0x6F, 0x2C) : 'EF_DCK',
|
||||||
|
(0x7F, 0x20, 0x6F, 0x30) : 'EF_PLMNsel',
|
||||||
|
(0x7F, 0x20, 0x6F, 0x31) : 'EF_HPLMN',
|
||||||
|
(0x7F, 0x20, 0x6F, 0x32) : 'EF_CNL',
|
||||||
|
(0x7F, 0x20, 0x6F, 0x37) : 'EF_ACMmax',
|
||||||
|
(0x7F, 0x20, 0x6F, 0x38) : 'EF_SST',
|
||||||
|
(0x7F, 0x20, 0x6F, 0x39) : 'EF_ACM',
|
||||||
|
(0x7F, 0x20, 0x6F, 0x3E) : 'EF_GID1',
|
||||||
|
(0x7F, 0x20, 0x6F, 0x3F) : 'EF_GID2',
|
||||||
|
(0x7F, 0x20, 0x6F, 0x41) : 'EF_PUCT',
|
||||||
|
(0x7F, 0x20, 0x6F, 0x45) : 'EF_CBMI',
|
||||||
|
(0x7F, 0x20, 0x6F, 0x46) : 'EF_SPN',
|
||||||
|
(0x7F, 0x20, 0x6F, 0x48) : 'EF_CBMID',
|
||||||
|
(0x7F, 0x20, 0x6F, 0x74) : 'EF_BCCH',
|
||||||
|
(0x7F, 0x20, 0x6F, 0x78) : 'EF_ACC',
|
||||||
|
(0x7F, 0x20, 0x6F, 0x7B) : 'EF_FPLMN',
|
||||||
|
(0x7F, 0x20, 0x6F, 0x7E) : 'EF_LOCI',
|
||||||
|
(0x7F, 0x20, 0x6F, 0xAD) : 'EF_AD',
|
||||||
|
(0x7F, 0x20, 0x6F, 0xAE) : 'EF_PHASE',
|
||||||
|
(0x7F, 0x20, 0x6F, 0xB1) : 'EF_VGCS',
|
||||||
|
(0x7F, 0x20, 0x6F, 0xB2) : 'EF_VGCSS',
|
||||||
|
(0x7F, 0x20, 0x6F, 0xB3) : 'EF_VBS',
|
||||||
|
(0x7F, 0x20, 0x6F, 0xB4) : 'EF_VBSS',
|
||||||
|
(0x7F, 0x20, 0x6F, 0xB5) : 'EF_eMLPP',
|
||||||
|
(0x7F, 0x20, 0x6F, 0xB6) : 'EF_AAeM',
|
||||||
|
(0x7F, 0x20, 0x6F, 0xB7) : 'EF_ECC',
|
||||||
|
(0x7F, 0x20, 0x6F, 0x50) : 'EF_CBMIR',
|
||||||
|
(0x7F, 0x20, 0x6F, 0x51) : 'EF_NIA',
|
||||||
|
(0x7F, 0x20, 0x6F, 0x52) : 'EF_KcGPRS',
|
||||||
|
(0x7F, 0x20, 0x6F, 0x53) : 'EF_LOCIGPRS',
|
||||||
|
(0x7F, 0x20, 0x6F, 0x54) : 'EF_SUME',
|
||||||
|
(0x7F, 0x20, 0x6F, 0x60) : 'EF_PLMNwAcT',
|
||||||
|
(0x7F, 0x20, 0x6F, 0x61) : 'EF_OPLMNwAcT',
|
||||||
|
(0x7F, 0x20, 0x6F, 0x62) : 'EF_HPLMNAcT',
|
||||||
|
(0x7F, 0x20, 0x6F, 0x63) : 'EF_CPBCCH',
|
||||||
|
(0x7F, 0x20, 0x6F, 0x64) : 'EF_INVSCAN',
|
||||||
|
(0x7F, 0x20, 0x6F, 0xC5) : 'EF_PNN',
|
||||||
|
(0x7F, 0x20, 0x6F, 0xC6) : 'EF_OPL',
|
||||||
|
(0x7F, 0x20, 0x6F, 0xC7) : 'EF_MBDN',
|
||||||
|
(0x7F, 0x20, 0x6F, 0xC8) : 'EF_EXT6',
|
||||||
|
(0x7F, 0x20, 0x6F, 0xC9) : 'EF_MBI',
|
||||||
|
(0x7F, 0x20, 0x6F, 0xCA) : 'EF_MWIS',
|
||||||
|
(0x7F, 0x20, 0x6F, 0xCB) : 'EF_CFIS',
|
||||||
|
(0x7F, 0x20, 0x6F, 0xCC) : 'EF_EXT7',
|
||||||
|
(0x7F, 0x20, 0x6F, 0xCD) : 'EF_SPDI',
|
||||||
|
(0x7F, 0x20, 0x6F, 0xCE) : 'EF_MMSN',
|
||||||
|
(0x7F, 0x20, 0x6F, 0xCF) : 'EF_EXT8',
|
||||||
|
(0x7F, 0x20, 0x6F, 0xD0) : 'EF_MMSICP',
|
||||||
|
(0x7F, 0x20, 0x6F, 0xD1) : 'EF_MMSUP',
|
||||||
|
(0x7F, 0x20, 0x6F, 0xD2) : 'EF_MMSUCP',
|
||||||
|
}
|
||||||
|
|
||||||
|
USIM_FS = {
|
||||||
|
(0x3F, 0x00) : 'MF',
|
||||||
|
(0x2F, 0x00) : 'EF_DIR',
|
||||||
|
(0x2F, 0x05) : 'EF_PL',
|
||||||
|
(0x2F, 0x06) : 'EF_ARR',
|
||||||
|
(0x2F, 0xE2) : 'EF_ICCID',
|
||||||
|
(0x7F, 0x20) : 'DF_GSM',
|
||||||
|
(0x7F, 0x10) : 'DF_TELECOM',
|
||||||
|
(0x7F, 0x10, 0x6F, 0x06) : 'EF_ARR',
|
||||||
|
(0x7F, 0x10, 0x6F, 0x3A) : 'EF_ADN',
|
||||||
|
(0x7F, 0x10, 0x6F, 0x3B) : 'EF_FDN',
|
||||||
|
(0x7F, 0x10, 0x6F, 0x3C) : 'EF_SMS',
|
||||||
|
(0x7F, 0x10, 0x6F, 0x4F) : 'EF_ECCP',
|
||||||
|
(0x7F, 0x10, 0x6F, 0x40) : 'EF_MSISDN',
|
||||||
|
(0x7F, 0x10, 0x6F, 0x42) : 'EF_SMSP',
|
||||||
|
(0x7F, 0x10, 0x6F, 0x43) : 'EF_SMSS',
|
||||||
|
(0x7F, 0x10, 0x6F, 0x44) : 'EF_LND',
|
||||||
|
(0x7F, 0x10, 0x6F, 0x47) : 'EF_SMSR',
|
||||||
|
(0x7F, 0x10, 0x6F, 0x49) : 'EF_SDN',
|
||||||
|
(0x7F, 0x10, 0x6F, 0x4A) : 'EF_EXT1',
|
||||||
|
(0x7F, 0x10, 0x6F, 0x4B) : 'EF_EXT2',
|
||||||
|
(0x7F, 0x10, 0x6F, 0x4C) : 'EF_EXT3',
|
||||||
|
(0x7F, 0x10, 0x6F, 0x4D) : 'EF_BDN',
|
||||||
|
(0x7F, 0x10, 0x6F, 0x4E) : 'EF_EXT4',
|
||||||
|
(0x7F, 0x10, 0x6F, 0x53) : 'EF_RMA',
|
||||||
|
(0x7F, 0x10, 0x6F, 0x54) : 'EF_SUME',
|
||||||
|
(0x7F, 0x10, 0x5F, 0x50) : 'DF_GRAPHICS',
|
||||||
|
(0x7F, 0x10, 0x5F, 0x50, 0x4F, 0x20) : 'EF_IMG',
|
||||||
|
#(0x7F, 0x10, 0x5F, 0x50, 0x4F, 0xXX) : 'EF_IIDFn',
|
||||||
|
(0x5F, 0x3A) : 'DF_PHONEBOOK',
|
||||||
|
(0x5F, 0x3A, 0x4F, 0x30) : 'EF_PBR',
|
||||||
|
#(0x5F, 0x3A, 0x4F, 0xXX) : 'EF_IAP',
|
||||||
|
#(0x5F, 0x3A, 0x4F, 0xXX) : 'EF_ADN',
|
||||||
|
#(0x5F, 0x3A, 0x4F, 0xXX) : 'EF_EXT1',
|
||||||
|
#(0x5F, 0x3A, 0x4F, 0xXX) : 'EF_PBC',
|
||||||
|
#(0x5F, 0x3A, 0x4F, 0xXX) : 'EF_GRP',
|
||||||
|
#(0x5F, 0x3A, 0x4F, 0xXX) : 'EF_AAS',
|
||||||
|
#(0x5F, 0x3A, 0x4F, 0xXX) : 'EF_GAS',
|
||||||
|
#(0x5F, 0x3A, 0x4F, 0xXX) : 'EF_ANR',
|
||||||
|
#(0x5F, 0x3A, 0x4F, 0xXX) : 'EF_SNE',
|
||||||
|
#(0x5F, 0x3A, 0x4F, 0xXX) : 'EF_CCP1',
|
||||||
|
#(0x5F, 0x3A, 0x4F, 0xXX) : 'EF_UID',
|
||||||
|
(0x5F, 0x3A, 0x4F, 0x22) : 'EF_PSC',
|
||||||
|
(0x5F, 0x3A, 0x4F, 0x23) : 'EF_CC',
|
||||||
|
(0x5F, 0x3A, 0x4F, 0x24) : 'EF_PUID',
|
||||||
|
#(0x5F, 0x3A, 0x4F, 0xXX) : 'EF_EMAIL',
|
||||||
|
(0x5F, 0x3B) : 'DF_MULTIMEDIA',
|
||||||
|
(0x5F, 0x3B, 0x4F, 0x47) : 'EF_MML',
|
||||||
|
(0x5F, 0x3B, 0x4F, 0x48) : 'EF_MMDF',
|
||||||
|
}
|
||||||
|
|
||||||
|
# this MF_FS is a trick that concatenate SIM_FS and USIM_FS
|
||||||
|
# useful when bruteforcing the FS under MF whatever the type of card selected
|
||||||
|
MF_FS = {
|
||||||
|
(0x3F, 0x00) : 'MF',
|
||||||
|
(0x2F, 0x00) : 'EF_DIR',
|
||||||
|
(0x2F, 0x05) : 'EF_ELP',
|
||||||
|
(0x2F, 0x06) : 'EF_ARR',
|
||||||
|
(0x2F, 0xE2) : 'EF_ICCID',
|
||||||
|
(0x5F, 0x3A) : 'DF_PHONEBOOK',
|
||||||
|
(0x5F, 0x3A, 0x4F, 0x30) : 'EF_PBR',
|
||||||
|
#(0x5F, 0x3A, 0x4F, 0xXX) : 'EF_IAP',
|
||||||
|
#(0x5F, 0x3A, 0x4F, 0xXX) : 'EF_ADN',
|
||||||
|
#(0x5F, 0x3A, 0x4F, 0xXX) : 'EF_EXT1',
|
||||||
|
#(0x5F, 0x3A, 0x4F, 0xXX) : 'EF_PBC',
|
||||||
|
#(0x5F, 0x3A, 0x4F, 0xXX) : 'EF_GRP',
|
||||||
|
#(0x5F, 0x3A, 0x4F, 0xXX) : 'EF_AAS',
|
||||||
|
#(0x5F, 0x3A, 0x4F, 0xXX) : 'EF_GAS',
|
||||||
|
#(0x5F, 0x3A, 0x4F, 0xXX) : 'EF_ANR',
|
||||||
|
#(0x5F, 0x3A, 0x4F, 0xXX) : 'EF_SNE',
|
||||||
|
#(0x5F, 0x3A, 0x4F, 0xXX) : 'EF_CCP1',
|
||||||
|
#(0x5F, 0x3A, 0x4F, 0xXX) : 'EF_UID',
|
||||||
|
(0x5F, 0x3A, 0x4F, 0x22) : 'EF_PSC',
|
||||||
|
(0x5F, 0x3A, 0x4F, 0x23) : 'EF_CC',
|
||||||
|
(0x5F, 0x3A, 0x4F, 0x24) : 'EF_PUID',
|
||||||
|
#(0x5F, 0x3A, 0x4F, 0xXX) : 'EF_EMAIL',
|
||||||
|
(0x5F, 0x3B) : 'DF_MULTIMEDIA',
|
||||||
|
(0x5F, 0x3B, 0x4F, 0x47) : 'EF_MML',
|
||||||
|
(0x5F, 0x3B, 0x4F, 0x48) : 'EF_MMDF',
|
||||||
|
(0x7F, 0x10) : 'DF_TELECOM',
|
||||||
|
(0x7F, 0x10, 0x6F, 0x06) : 'EF_ARR',
|
||||||
|
(0x7F, 0x10, 0x6F, 0x3A) : 'EF_ADN',
|
||||||
|
(0x7F, 0x10, 0x6F, 0x3B) : 'EF_FDN',
|
||||||
|
(0x7F, 0x10, 0x6F, 0x3C) : 'EF_SMS',
|
||||||
|
(0x7F, 0x10, 0x6F, 0x3D) : 'EF_CCP',
|
||||||
|
(0x7F, 0x10, 0x6F, 0x40) : 'EF_MSISDN',
|
||||||
|
(0x7F, 0x10, 0x6F, 0x42) : 'EF_SMSP',
|
||||||
|
(0x7F, 0x10, 0x6F, 0x43) : 'EF_SMSS',
|
||||||
|
(0x7F, 0x10, 0x6F, 0x44) : 'EF_LND',
|
||||||
|
(0x7F, 0x10, 0x6F, 0x47) : 'EF_SMSR',
|
||||||
|
(0x7F, 0x10, 0x6F, 0x49) : 'EF_SDN',
|
||||||
|
(0x7F, 0x10, 0x6F, 0x4A) : 'EF_EXT1',
|
||||||
|
(0x7F, 0x10, 0x6F, 0x4B) : 'EF_EXT2',
|
||||||
|
(0x7F, 0x10, 0x6F, 0x4C) : 'EF_EXT3',
|
||||||
|
(0x7F, 0x10, 0x6F, 0x4D) : 'EF_BDN',
|
||||||
|
(0x7F, 0x10, 0x6F, 0x4E) : 'EF_EXT4',
|
||||||
|
(0x7F, 0x10, 0x6F, 0x4F) : 'EF_ECCP',
|
||||||
|
(0x7F, 0x10, 0x5F, 0x50) : 'DF_GRAPHICS',
|
||||||
|
(0x7F, 0x10, 0x5F, 0x50, 0x4F, 0x20) : 'EF_IMG',
|
||||||
|
(0x7F, 0x10, 0x6F, 0x53) : 'EF_RMA',
|
||||||
|
(0x7F, 0x10, 0x6F, 0x54) : 'EF_SUME',
|
||||||
|
(0x7F, 0x10, 0x6F, 0x58) : 'EF_CMI',
|
||||||
|
(0x7F, 0x20) : 'DF_GSM',
|
||||||
|
(0x7F, 0x20, 0x5F, 0x3C) : 'DF_MExE',
|
||||||
|
(0x7F, 0x20, 0x5F, 0x3C, 0x4F, 0x40) : 'EF_MExE-ST',
|
||||||
|
(0x7F, 0x20, 0x5F, 0x3C, 0x4F, 0x41) : 'EF_ORPK',
|
||||||
|
(0x7F, 0x20, 0x5F, 0x3C, 0x4F, 0x42) : 'EF_ARPK',
|
||||||
|
(0x7F, 0x20, 0x5F, 0x3C, 0x4F, 0x43) : 'EF_TPRPK',
|
||||||
|
(0x7F, 0x20, 0x5F, 0x30) : 'DF_IRIDIUM',
|
||||||
|
(0x7F, 0x20, 0x5F, 0x31) : 'DF_GLOBALSTAR',
|
||||||
|
(0x7F, 0x20, 0x5F, 0x32) : 'DF_ICO',
|
||||||
|
(0x7F, 0x20, 0x5F, 0x33) : 'DF_ACeS',
|
||||||
|
(0x7F, 0x20, 0x5F, 0x40) : 'DF_EIA/TIA-553',
|
||||||
|
(0x7F, 0x20, 0x5F, 0x60) : 'DF_CTS',
|
||||||
|
(0x7F, 0x20, 0x5F, 0x70) : 'DF_SoLSA',
|
||||||
|
(0x7F, 0x20, 0x5F, 0x70, 0x4F, 0x30) : 'EF_SAI',
|
||||||
|
(0x7F, 0x20, 0x5F, 0x70, 0x4F, 0x31) : 'EF_SLL',
|
||||||
|
(0x7F, 0x20, 0x6F, 0x05) : 'EF_LP',
|
||||||
|
(0x7F, 0x20, 0x6F, 0x07) : 'EF_IMSI',
|
||||||
|
(0x7F, 0x20, 0x6F, 0x20) : 'EF_Kc',
|
||||||
|
(0x7F, 0x20, 0x6F, 0x2C) : 'EF_DCK',
|
||||||
|
(0x7F, 0x20, 0x6F, 0x30) : 'EF_PLMNsel',
|
||||||
|
(0x7F, 0x20, 0x6F, 0x31) : 'EF_HPLMN',
|
||||||
|
(0x7F, 0x20, 0x6F, 0x32) : 'EF_CNL',
|
||||||
|
(0x7F, 0x20, 0x6F, 0x37) : 'EF_ACMmax',
|
||||||
|
(0x7F, 0x20, 0x6F, 0x38) : 'EF_SST',
|
||||||
|
(0x7F, 0x20, 0x6F, 0x39) : 'EF_ACM',
|
||||||
|
(0x7F, 0x20, 0x6F, 0x3E) : 'EF_GID1',
|
||||||
|
(0x7F, 0x20, 0x6F, 0x3F) : 'EF_GID2',
|
||||||
|
(0x7F, 0x20, 0x6F, 0x41) : 'EF_PUCT',
|
||||||
|
(0x7F, 0x20, 0x6F, 0x45) : 'EF_CBMI',
|
||||||
|
(0x7F, 0x20, 0x6F, 0x46) : 'EF_SPN',
|
||||||
|
(0x7F, 0x20, 0x6F, 0x48) : 'EF_CBMID',
|
||||||
|
(0x7F, 0x20, 0x6F, 0x74) : 'EF_BCCH',
|
||||||
|
(0x7F, 0x20, 0x6F, 0x78) : 'EF_ACC',
|
||||||
|
(0x7F, 0x20, 0x6F, 0x7B) : 'EF_FPLMN',
|
||||||
|
(0x7F, 0x20, 0x6F, 0x7E) : 'EF_LOCI',
|
||||||
|
(0x7F, 0x20, 0x6F, 0xAD) : 'EF_AD',
|
||||||
|
(0x7F, 0x20, 0x6F, 0xAE) : 'EF_PHASE',
|
||||||
|
(0x7F, 0x20, 0x6F, 0xB1) : 'EF_VGCS',
|
||||||
|
(0x7F, 0x20, 0x6F, 0xB2) : 'EF_VGCSS',
|
||||||
|
(0x7F, 0x20, 0x6F, 0xB3) : 'EF_VBS',
|
||||||
|
(0x7F, 0x20, 0x6F, 0xB4) : 'EF_VBSS',
|
||||||
|
(0x7F, 0x20, 0x6F, 0xB5) : 'EF_eMLPP',
|
||||||
|
(0x7F, 0x20, 0x6F, 0xB6) : 'EF_AAeM',
|
||||||
|
(0x7F, 0x20, 0x6F, 0xB7) : 'EF_ECC',
|
||||||
|
(0x7F, 0x20, 0x6F, 0x50) : 'EF_CBMIR',
|
||||||
|
(0x7F, 0x20, 0x6F, 0x51) : 'EF_NIA',
|
||||||
|
(0x7F, 0x20, 0x6F, 0x52) : 'EF_KcGPRS',
|
||||||
|
(0x7F, 0x20, 0x6F, 0x53) : 'EF_LOCIGPRS',
|
||||||
|
(0x7F, 0x20, 0x6F, 0x54) : 'EF_SUME',
|
||||||
|
(0x7F, 0x20, 0x6F, 0x60) : 'EF_PLMNwAcT',
|
||||||
|
(0x7F, 0x20, 0x6F, 0x61) : 'EF_OPLMNwAcT',
|
||||||
|
(0x7F, 0x20, 0x6F, 0x62) : 'EF_HPLMNAcT',
|
||||||
|
(0x7F, 0x20, 0x6F, 0x63) : 'EF_CPBCCH',
|
||||||
|
(0x7F, 0x20, 0x6F, 0x64) : 'EF_INVSCAN',
|
||||||
|
(0x7F, 0x20, 0x6F, 0xC5) : 'EF_PNN',
|
||||||
|
(0x7F, 0x20, 0x6F, 0xC6) : 'EF_OPL',
|
||||||
|
(0x7F, 0x20, 0x6F, 0xC7) : 'EF_MBDN',
|
||||||
|
(0x7F, 0x20, 0x6F, 0xC8) : 'EF_EXT6',
|
||||||
|
(0x7F, 0x20, 0x6F, 0xC9) : 'EF_MBI',
|
||||||
|
(0x7F, 0x20, 0x6F, 0xCA) : 'EF_MWIS',
|
||||||
|
(0x7F, 0x20, 0x6F, 0xCB) : 'EF_CFIS',
|
||||||
|
(0x7F, 0x20, 0x6F, 0xCC) : 'EF_EXT7',
|
||||||
|
(0x7F, 0x20, 0x6F, 0xCD) : 'EF_SPDI',
|
||||||
|
(0x7F, 0x20, 0x6F, 0xCE) : 'EF_MMSN',
|
||||||
|
(0x7F, 0x20, 0x6F, 0xCF) : 'EF_EXT8',
|
||||||
|
(0x7F, 0x20, 0x6F, 0xD0) : 'EF_MMSICP',
|
||||||
|
(0x7F, 0x20, 0x6F, 0xD1) : 'EF_MMSUP',
|
||||||
|
(0x7F, 0x20, 0x6F, 0xD2) : 'EF_MMSUCP',
|
||||||
|
(0x7F, 0x23) : 'DF_FP-CTS',
|
||||||
|
(0x7F, 0x22) : 'DF_IS-41',
|
||||||
|
}
|
||||||
|
|
||||||
|
USIM_app_FS = {
|
||||||
|
(0x5F, 0x40) : 'DF_WLAN',
|
||||||
|
(0x5F, 0x40, 0x4F, 0x41) : 'EF_Pseudo',
|
||||||
|
(0x5F, 0x40, 0x4F, 0x42) : 'EF_UPLMNWLAN',
|
||||||
|
(0x5F, 0x40, 0x4F, 0x43) : 'EF_0PLMNWLAN',
|
||||||
|
(0x5F, 0x40, 0x4F, 0x44) : 'EF_USSIDL',
|
||||||
|
(0x5F, 0x40, 0x4F, 0x45) : 'EF_OSSIDL',
|
||||||
|
(0x5F, 0x40, 0x4F, 0x46) : 'EF_WRI',
|
||||||
|
(0x5F, 0x70) : 'DF_SoLSA',
|
||||||
|
(0x5F, 0x70, 0x4F, 0x30) : 'EF_SAI',
|
||||||
|
(0x5F, 0x70, 0x4F, 0x31) : 'EF_SLL',
|
||||||
|
(0x5F, 0x3C) : 'DF_MExE',
|
||||||
|
(0x5F, 0x3C, 0x4F, 0x40) : 'EF_MExE-ST',
|
||||||
|
(0x5F, 0x3C, 0x4F, 0x41) : 'EF_ORPK',
|
||||||
|
(0x5F, 0x3C, 0x4F, 0x42) : 'EF_ARPK',
|
||||||
|
(0x5F, 0x3C, 0x4F, 0x43) : 'EF_TPRK',
|
||||||
|
#(0x5F, 0x3C, 0x4F, 0xXX) : 'EF_TKCDF',
|
||||||
|
(0x5F, 0x3B) : 'DF_GSM-ACCESS',
|
||||||
|
(0x5F, 0x3B, 0x4F, 0x20) : 'EF_Kc',
|
||||||
|
(0x5F, 0x3B, 0x4F, 0x52) : 'EF_KcGPRS',
|
||||||
|
(0x5F, 0x3B, 0x4F, 0x63) : 'EF_CPBCCH',
|
||||||
|
(0x5F, 0x3B, 0x4F, 0x64) : 'EF_invSCAN',
|
||||||
|
(0x5F, 0x3A) : 'DF_PHONEBOOK',
|
||||||
|
#(0x5F, 0x3A, 0x4F, 0xXX) : 'EF_UID',
|
||||||
|
(0x5F, 0x3A, 0x4F, 0x22) : 'EF_PSC',
|
||||||
|
(0x5F, 0x3A, 0x4F, 0x23) : 'EF_CC',
|
||||||
|
(0x5F, 0x3A, 0x4F, 0x24) : 'EF_PUID',
|
||||||
|
(0x5F, 0x3A, 0x4F, 0x30) : 'EF_PBR',
|
||||||
|
#(0x5F, 0x3A, 0x4F, 0xXX) : 'EF_CCP1',
|
||||||
|
#(0x5F, 0x3A, 0x4F, 0xXX) : 'EF_IAP',
|
||||||
|
#(0x5F, 0x3A, 0x4F, 0xXX) : 'EF_ADN',
|
||||||
|
#(0x5F, 0x3A, 0x4F, 0xXX) : 'EF_EXT1',
|
||||||
|
#(0x5F, 0x3A, 0x4F, 0xXX) : 'EF_PBC',
|
||||||
|
#(0x5F, 0x3A, 0x4F, 0xXX) : 'EF_GRP',
|
||||||
|
#(0x5F, 0x3A, 0x4F, 0xXX) : 'EF_AAS',
|
||||||
|
#(0x5F, 0x3A, 0x4F, 0xXX) : 'EF_GAS',
|
||||||
|
#(0x5F, 0x3A, 0x4F, 0xXX) : 'EF_ANR',
|
||||||
|
#(0x5F, 0x3A, 0x4F, 0xXX) : 'EF_SNE',
|
||||||
|
#(0x5F, 0x3A, 0x4F, 0xXX) : 'EF_EMAIL',
|
||||||
|
(0x6F, 0x05) : 'EF_LI',
|
||||||
|
(0x6F, 0x06) : 'EF_ARR',
|
||||||
|
(0x6F, 0x07) : 'EF_IMSI',
|
||||||
|
(0x6F, 0x08) : 'EF_Keys',
|
||||||
|
(0x6F, 0x09) : 'EF_KeysPS',
|
||||||
|
(0x6F, 0x2C) : 'EF_DCK',
|
||||||
|
(0x6F, 0x31) : 'EF_HPLMN',
|
||||||
|
(0x6F, 0x32) : 'EF_CNL',
|
||||||
|
(0x6F, 0x37) : 'EF_ACMmax',
|
||||||
|
(0x6F, 0x38) : 'EF_UST',
|
||||||
|
(0x6F, 0x39) : 'EF_ACM',
|
||||||
|
(0x6F, 0x3B) : 'EF_FDN',
|
||||||
|
(0x6F, 0x3C) : 'EF_SMS',
|
||||||
|
(0x6F, 0x3E) : 'EF_GID1',
|
||||||
|
(0x6F, 0x3F) : 'EF_GID2',
|
||||||
|
(0x6F, 0x40) : 'EF_MSISDN',
|
||||||
|
(0x6F, 0x41) : 'EF_PUCT',
|
||||||
|
(0x6F, 0x42) : 'EF_SMSP',
|
||||||
|
(0x6F, 0x43) : 'EF_SMSS',
|
||||||
|
(0x6F, 0x45) : 'EF_CBMI',
|
||||||
|
(0x6F, 0x46) : 'EF_SPN',
|
||||||
|
(0x6F, 0x47) : 'EF_SMSR',
|
||||||
|
(0x6F, 0x48) : 'EF_CBMID',
|
||||||
|
(0x6F, 0x49) : 'EF_SDN',
|
||||||
|
(0x6F, 0x4B) : 'EF_EXT2',
|
||||||
|
(0x6F, 0x4C) : 'EF_EXT3',
|
||||||
|
(0x6F, 0x4D) : 'EF_BDN',
|
||||||
|
(0x6F, 0x4E) : 'EF_EXT5',
|
||||||
|
(0x6F, 0x50) : 'EF_CBMIR',
|
||||||
|
(0x6F, 0x55) : 'EF_EXT4',
|
||||||
|
(0x6F, 0x56) : 'EF_EST',
|
||||||
|
(0x6F, 0x57) : 'EF_ACL',
|
||||||
|
(0x6F, 0x58) : 'EF_CMI',
|
||||||
|
(0x6F, 0x5B) : 'EF_START-HFN',
|
||||||
|
(0x6F, 0x5C) : 'EF_THRESHOLD',
|
||||||
|
(0x6F, 0x60) : 'EF_PLMNwAcT',
|
||||||
|
(0x6F, 0x61) : 'EF_OPLMNwAcT',
|
||||||
|
(0x6F, 0x62) : 'EF_HPLMNwAcT',
|
||||||
|
(0x6F, 0xD9) : 'EF_EHPLMN',
|
||||||
|
(0x6F, 0x73) : 'EF_PSLOCI',
|
||||||
|
(0x6F, 0x78) : 'EF_ACC',
|
||||||
|
(0x6F, 0x7B) : 'EF_FPLMN',
|
||||||
|
(0x6F, 0x7E) : 'EF_LOCI',
|
||||||
|
(0x6F, 0x80) : 'EF_ICI',
|
||||||
|
(0x6F, 0x81) : 'EF_OCI',
|
||||||
|
(0x6F, 0x82) : 'EF_ICT',
|
||||||
|
(0x6F, 0x83) : 'EF_OCT',
|
||||||
|
(0x6F, 0xAD) : 'EF_AD',
|
||||||
|
(0x6F, 0xB5) : 'EF_eMLPP',
|
||||||
|
(0x6F, 0xB6) : 'EF_AAeM',
|
||||||
|
(0x6F, 0xB7) : 'EF_ECC',
|
||||||
|
(0x6F, 0xC3) : 'EF_Hiddenkey',
|
||||||
|
(0x6F, 0xC4) : 'EF_NETPAR',
|
||||||
|
(0x6F, 0xC5) : 'EF_PNN',
|
||||||
|
(0x6F, 0xC6) : 'EF_OPL',
|
||||||
|
(0x6F, 0xC7) : 'EF_MBDN',
|
||||||
|
(0x6F, 0xC8) : 'EF_EXT6',
|
||||||
|
(0x6F, 0xC9) : 'EF_MBI',
|
||||||
|
(0x6F, 0xCA) : 'EF_MWIS',
|
||||||
|
(0x6F, 0xCB) : 'EF_CFIS',
|
||||||
|
(0x6F, 0xCC) : 'EF_EXT7',
|
||||||
|
(0x6F, 0xCD) : 'EF_SPDI',
|
||||||
|
(0x6F, 0xCE) : 'EF_MMSN',
|
||||||
|
(0x6F, 0xCF) : 'EF_EXT8',
|
||||||
|
(0x6F, 0xD0) : 'EF_MMSICP',
|
||||||
|
(0x6F, 0xD1) : 'EF_MMSUP',
|
||||||
|
(0x6F, 0xD2) : 'EF_MMSUCP',
|
||||||
|
(0x6F, 0xD3) : 'EF_NIA',
|
||||||
|
(0x6F, 0x4F) : 'EF_CCP2',
|
||||||
|
(0x6F, 0xB1) : 'EF_VGCS',
|
||||||
|
(0x6F, 0xB2) : 'EF_VGCSS',
|
||||||
|
(0x6F, 0xB3) : 'EF_VBS',
|
||||||
|
(0x6F, 0xB4) : 'EF_VBSS',
|
||||||
|
(0x6F, 0xD4) : 'EF_VGCSCA',
|
||||||
|
(0x6F, 0xD5) : 'EF_VBSCA',
|
||||||
|
(0x6F, 0xD6) : 'EF_GBAP',
|
||||||
|
(0x6F, 0xD7) : 'EF_MSK',
|
||||||
|
(0x6F, 0xD8) : 'EF_MUK',
|
||||||
|
(0x6F, 0xDA) : 'EF_GBANL',
|
||||||
|
(0x6F, 0xDB) : 'EF_EHPLMNPI',
|
||||||
|
(0x6F, 0xDC) : 'EF_LRPLMNSI',
|
||||||
|
(0x6F, 0xDD) : 'EF_NAFKCA',
|
||||||
|
(0x6F, 0xDE) : 'EF_SPNI',
|
||||||
|
(0x6F, 0xDF) : 'EF_PNNI',
|
||||||
|
}
|
||||||
|
|
||||||
|
# Actually, those DF can be under MF, ADF_USIM or DF_TELECOM:
|
||||||
|
DF_PHONEBOOK = {
|
||||||
|
(0x5F, 0x3A) : 'DF_PHONEBOOK',
|
||||||
|
(0x5F, 0x3A, 0x4F) : 'EF_UID',
|
||||||
|
(0x5F, 0x3A, 0x4F, 0x22) : 'EF_PSC',
|
||||||
|
(0x5F, 0x3A, 0x4F, 0x23) : 'EF_CC',
|
||||||
|
(0x5F, 0x3A, 0x4F, 0x24) : 'EF_PUID',
|
||||||
|
(0x5F, 0x3A, 0x4F, 0x30) : 'EF_PBR',
|
||||||
|
(0x5F, 0x3A, 0x4F) : 'EF_CCP1',
|
||||||
|
(0x5F, 0x3A, 0x4F) : 'EF_IAP',
|
||||||
|
(0x5F, 0x3A, 0x4F) : 'EF_ADN',
|
||||||
|
(0x5F, 0x3A, 0x4F) : 'EF_EXT1',
|
||||||
|
(0x5F, 0x3A, 0x4F) : 'EF_PBC',
|
||||||
|
(0x5F, 0x3A, 0x4F) : 'EF_GRP',
|
||||||
|
(0x5F, 0x3A, 0x4F) : 'EF_AAS',
|
||||||
|
(0x5F, 0x3A, 0x4F) : 'EF_GAS',
|
||||||
|
(0x5F, 0x3A, 0x4F) : 'EF_ANR',
|
||||||
|
(0x5F, 0x3A, 0x4F) : 'EF_SNE',
|
||||||
|
(0x5F, 0x3A, 0x4F) : 'EF_EMAIL',
|
||||||
|
}
|
||||||
|
|
||||||
|
DF_GRAPHICS = {
|
||||||
|
(0x5F, 0x50) : 'DF_GRAPHICS',
|
||||||
|
(0x5F, 0x50, 0x4F, 0x20) : 'EF_IMG',
|
||||||
|
}
|
||||||
|
|
||||||
|
DF_MULTIMEDIA = {
|
||||||
|
(0x5F, 0x3B) : 'DF_MULTIMEDIA',
|
||||||
|
(0x5F, 0x3B, 0x4F, 0x47) : 'EF_MML',
|
||||||
|
(0x5F, 0x3B, 0x4F, 0x48) : 'EF_MMDF',
|
||||||
|
}
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,456 @@
|
||||||
|
# -*- coding: UTF-8 -*-
|
||||||
|
"""
|
||||||
|
card: Library adapted to request (U)SIM cards and other types of telco cards.
|
||||||
|
Copyright (C) 2010 Benoit Michau
|
||||||
|
|
||||||
|
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, write to the Free Software Foundation, Inc.,
|
||||||
|
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||||
|
"""
|
||||||
|
|
||||||
|
#################################
|
||||||
|
# Python library to work on
|
||||||
|
# SIM card
|
||||||
|
# communication based on ISO7816 card
|
||||||
|
#
|
||||||
|
# needs pyscard from:
|
||||||
|
# http://pyscard.sourceforge.net/
|
||||||
|
#################################
|
||||||
|
|
||||||
|
from card.ICC import ISO7816
|
||||||
|
from card.FS import SIM_FS, MF_FS
|
||||||
|
from card.utils import *
|
||||||
|
|
||||||
|
SIM_service_table = {
|
||||||
|
1 : "CHV1 disable function",
|
||||||
|
2 : "Abbreviated Dialling Numbers (ADN)",
|
||||||
|
3 : "Fixed Dialling Numbers (FDN)",
|
||||||
|
4 : "Short Message Storage (SMS)",
|
||||||
|
5 : "Advice of Charge (AoC)",
|
||||||
|
6 : "Capability Configuration Parameters (CCP)",
|
||||||
|
7 : "PLMN selector",
|
||||||
|
8 : "RFU",
|
||||||
|
9 : "MSISDN",
|
||||||
|
10 : "Extension1",
|
||||||
|
11 : "Extension2",
|
||||||
|
12 : "SMS Parameters",
|
||||||
|
13 : "Last Number Dialled (LND)",
|
||||||
|
14 : "Cell Broadcast Message Identifier",
|
||||||
|
15 : "Group Identifier Level 1",
|
||||||
|
16 : "Group Identifier Level 2",
|
||||||
|
17 : "Service Provider Name",
|
||||||
|
18 : "Service Dialling Numbers (SDN)",
|
||||||
|
19 : "Extension3",
|
||||||
|
20 : "RFU",
|
||||||
|
21 : "VGCS Group Identifier List (EFVGCS and EFVGCSS)",
|
||||||
|
22 : "VBS Group Identifier List (EFVBS and EFVBSS)",
|
||||||
|
23 : "enhanced Multi-Level Precedence and Pre-emption Service",
|
||||||
|
24 : "Automatic Answer for eMLPP",
|
||||||
|
25 : "Data download via SMS-CB",
|
||||||
|
26 : "Data download via SMS-PP",
|
||||||
|
27 : "Menu selection",
|
||||||
|
28 : "Call control",
|
||||||
|
29 : "Proactive SIM",
|
||||||
|
30 : "Cell Broadcast Message Identifier Ranges",
|
||||||
|
31 : "Barred Dialling Numbers (BDN)",
|
||||||
|
32 : "Extension4",
|
||||||
|
33 : "De-personalization Control Keys",
|
||||||
|
34 : "Co-operative Network List",
|
||||||
|
35 : "Short Message Status Reports",
|
||||||
|
36 : "Network's indication of alerting in the MS ",
|
||||||
|
37 : "Mobile Originated Short Message control by SIM ",
|
||||||
|
38 : "GPRS",
|
||||||
|
39 : "Image (IMG)",
|
||||||
|
40 : "SoLSA (Support of Local Service Area)",
|
||||||
|
41 : "USSD string data object supported in Call Control",
|
||||||
|
42 : "RUN AT COMMAND command",
|
||||||
|
43 : "User controlled PLMN Selector with Access Technology",
|
||||||
|
44 : "Operator controlled PLMN Selector with Access Technology",
|
||||||
|
45 : "HPLMN Selector with Access Technology",
|
||||||
|
46 : "CPBCCH Information",
|
||||||
|
47 : "Investigation Scan",
|
||||||
|
48 : "Extended Capability Configuration Parameters",
|
||||||
|
49 : "MExE",
|
||||||
|
50 : "RPLMN last used Access Technology",
|
||||||
|
51 : "PLMN Network Name",
|
||||||
|
52 : "Operator PLMN List",
|
||||||
|
53 : "Mailbox Dialling Numbers ",
|
||||||
|
54 : "Message Waiting Indication Status",
|
||||||
|
55 : "Call Forwarding Indication Status",
|
||||||
|
56 : "Service Provider Display Information",
|
||||||
|
}
|
||||||
|
|
||||||
|
class SIM(ISO7816):
|
||||||
|
'''
|
||||||
|
define attributes, methods and facilities for ETSI / 3GPP SIM card
|
||||||
|
check SIM specifications in ETSI TS 102.221 and 3GPP TS 51.011
|
||||||
|
|
||||||
|
inherit methods and objects from ISO7816 class
|
||||||
|
use self.dbg = 1 or more to print live debugging information
|
||||||
|
'''
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
'''
|
||||||
|
initialize like an ISO7816-4 card with CLA=0xA0
|
||||||
|
can also be used for USIM working in SIM mode,
|
||||||
|
'''
|
||||||
|
ISO7816.__init__(self, CLA=0xA0)
|
||||||
|
|
||||||
|
if self.dbg >= 2:
|
||||||
|
log(3, '(SIM.__init__) type definition: %s' % type(self))
|
||||||
|
log(3, '(SIM.__init__) CLA definition: %s' % hex(self.CLA))
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def sw_status(sw1, sw2):
|
||||||
|
'''
|
||||||
|
sw_status(sw1=int, sw2=int) -> string
|
||||||
|
|
||||||
|
extends SW status bytes interpretation from ISO7816
|
||||||
|
with ETSI / 3GPP SW codes
|
||||||
|
helps to speak with the smartcard!
|
||||||
|
'''
|
||||||
|
status = ISO7816.sw_status(sw1, sw2)
|
||||||
|
if sw1 == 0x91: status = 'normal processing, with extra info ' \
|
||||||
|
'containing a command for the terminal: length of the ' \
|
||||||
|
'response data %d' % sw2
|
||||||
|
elif sw1 == 0x9E: status = 'normal processing, SIM data download ' \
|
||||||
|
'error: length of the response data %d' % sw2
|
||||||
|
elif sw1 == 0x9F: status = 'normal processing: length of the ' \
|
||||||
|
'response data %d' % sw2
|
||||||
|
elif (sw1, sw2) == (0x93, 0x00): status = 'SIM application toolkit ' \
|
||||||
|
'busy, command cannot be executed at present'
|
||||||
|
elif sw1 == 0x92 :
|
||||||
|
status = 'memory management'
|
||||||
|
if sw2 < 16: status += ': command successful but after %d '\
|
||||||
|
'retry routine' % sw2
|
||||||
|
elif sw2 == 0x40: status += ': memory problem'
|
||||||
|
elif sw1 == 0x94:
|
||||||
|
status = 'referencing management'
|
||||||
|
if sw2 == 0x00: status += ': no EF selected'
|
||||||
|
elif sw2 == 0x02: status += ': out of range (invalid address)'
|
||||||
|
elif sw2 == 0x04: status += ': file ID or pattern not found'
|
||||||
|
elif sw2 == 0x08: status += ': file inconsistent with the command'
|
||||||
|
elif sw1 == 0x98:
|
||||||
|
status = 'security management'
|
||||||
|
if sw2 == 0x02: status += ': no CHV initialized'
|
||||||
|
elif sw2 == 0x04: status += ': access condition not fulfilled, ' \
|
||||||
|
'at least 1 attempt left'
|
||||||
|
elif sw2 == 0x08: status += ': in contradiction with CHV status'
|
||||||
|
elif sw2 == 0x10: status += ': in contradiction with ' \
|
||||||
|
'invalidation status'
|
||||||
|
elif sw2 == 0x40: status += ': unsuccessful CHV verification, ' \
|
||||||
|
'no attempt left'
|
||||||
|
elif sw2 == 0x50: status += ': increase cannot be performed, ' \
|
||||||
|
'max value reached'
|
||||||
|
elif sw2 == 0x62: status += ': authentication error, ' \
|
||||||
|
'application specific'
|
||||||
|
elif sw2 == 0x63: status += ': security session expired'
|
||||||
|
return status
|
||||||
|
|
||||||
|
def verify_pin(self, pin='', pin_type=1):
|
||||||
|
'''
|
||||||
|
verify CHV1 (PIN code) or CHV2 with VERIFY APDU command
|
||||||
|
call ISO7816 VERIFY method
|
||||||
|
'''
|
||||||
|
if pin_type in [1, 2] and type(pin) is str and \
|
||||||
|
len(pin) == 4 and 0 <= int(pin) < 10000:
|
||||||
|
PIN = [ord(i) for i in pin] + [0xFF, 0xFF, 0xFF, 0xFF]
|
||||||
|
self.coms.push( self.VERIFY(P2=pin_type, Data=PIN) )
|
||||||
|
else:
|
||||||
|
if self.dbg:
|
||||||
|
log(2, '(verify_pin) bad input parameters')
|
||||||
|
|
||||||
|
def disable_pin(self, pin='', pin_type=1):
|
||||||
|
'''
|
||||||
|
disable CHV1 (PIN code) or CHV2 with DISABLE_CHV APDU command
|
||||||
|
TIP: do it as soon as you can when you are working
|
||||||
|
with a SIM / USIM card for which you know the PIN!
|
||||||
|
call ISO7816 DISABLE method
|
||||||
|
'''
|
||||||
|
if pin_type in [1, 2] and type(pin) is str and \
|
||||||
|
len(pin) == 4 and 0 <= int(pin) < 10000:
|
||||||
|
PIN = [ord(i) for i in pin] + [0xFF, 0xFF, 0xFF, 0xFF]
|
||||||
|
self.coms.push( self.DISABLE_CHV(P2=pin_type, Data=PIN) )
|
||||||
|
else:
|
||||||
|
if self.dbg:
|
||||||
|
log(2, '(disable_pin) bad input parameters')
|
||||||
|
|
||||||
|
def enable_pin(self, pin='', pin_type=1):
|
||||||
|
'''
|
||||||
|
enable CHV1 (PIN code) or CHV2 with ENABLE_CHV APDU command
|
||||||
|
call ISO7816 ENABLE method
|
||||||
|
'''
|
||||||
|
if pin_type in [1, 2] and type(pin) is str and \
|
||||||
|
len(pin) == 4 and 0 <= int(pin) < 10000:
|
||||||
|
PIN = [ord(i) for i in pin] + [0xFF, 0xFF, 0xFF, 0xFF]
|
||||||
|
self.coms.push( self.ENABLE_CHV(P2=pin_type, Data=PIN) )
|
||||||
|
else:
|
||||||
|
if self.dbg:
|
||||||
|
log(2, '(enable_pin) bad input parameters')
|
||||||
|
|
||||||
|
def unblock_pin(self, pin_type=1, unblock_pin=''):
|
||||||
|
'''
|
||||||
|
WARNING: not correctly implemented!!!
|
||||||
|
and PUK are in general 8 nums...
|
||||||
|
TODO: make it correctly!
|
||||||
|
APDU Tx de-activated
|
||||||
|
|
||||||
|
unblock CHV1 (PIN code) or CHV2 with UNBLOCK_CHV APDU command
|
||||||
|
and set 0000 value for new PIN
|
||||||
|
call ISO7816 UNBLOCK_CHV method
|
||||||
|
'''
|
||||||
|
log(1, '(unblock_pin) not implemented: aborting')
|
||||||
|
return
|
||||||
|
#if pin_type == 1:
|
||||||
|
# pin_type = 0
|
||||||
|
if pin_type in [0, 2] and type(unblock_pin) is str and \
|
||||||
|
len(unblock_pin) == 4 and 0 <= int(unblock_pin) < 10000:
|
||||||
|
UNBL_PIN = [ord(i) for i in unblock_pin] + [0xFF, 0xFF, 0xFF, 0xFF]
|
||||||
|
#self.coms.push( self.UNBLOCK_CHV(P2=pin_type, Lc=0x10, \
|
||||||
|
# Data=UNBL_PIN + \
|
||||||
|
# [0x30, 0x30, 0x30, 0x30, 0xFF, 0xFF, 0xFF, 0xFF]) )
|
||||||
|
else:
|
||||||
|
if self.dbg:
|
||||||
|
log(2, '(unblock_pin) bad input parameters')
|
||||||
|
#return self.UNBLOCK_CHV(P2=pin_type)
|
||||||
|
|
||||||
|
def parse_file(self, Data=[]):
|
||||||
|
'''
|
||||||
|
parse_file(Data=[0x12, 0x34, 0x56, 0x89]) -> dict(file)
|
||||||
|
|
||||||
|
parses a list of bytes returned when selecting a file
|
||||||
|
interprets the content of some informative bytes for right accesses,
|
||||||
|
type / format of file... see TS 51.011
|
||||||
|
works over the SIM file structure
|
||||||
|
'''
|
||||||
|
fil = {}
|
||||||
|
fil['Size'] = Data[2]*0x100 + Data[3]
|
||||||
|
fil['File Identifier'] = Data[4:6]
|
||||||
|
fil['Type'] = ('RFU', 'MF', 'DF', '', 'EF')[Data[6]]
|
||||||
|
fil['Length'] = Data[12]
|
||||||
|
if fil['Type'] == 'MF' or fil['Type'] == 'DF':
|
||||||
|
fil['DF_num'] = Data[14]
|
||||||
|
fil['EF_num'] = Data[15]
|
||||||
|
fil['codes_num'] = Data[16]
|
||||||
|
fil['CHV1'] = ('not initialized','initialized')\
|
||||||
|
[(Data[18] & 0x80) / 0x80]\
|
||||||
|
+ ': %d attempts remain' % (Data[18] & 0x0F)
|
||||||
|
fil['unblock_CHV1'] = ('not initialized','initialized')\
|
||||||
|
[(Data[19] & 0x80) / 0x80]\
|
||||||
|
+ ': %d attempts remain' % (Data[19] & 0x0F)
|
||||||
|
fil['CHV2'] = ('not initialized','initialized')\
|
||||||
|
[(Data[20] & 0x80) / 0x80]\
|
||||||
|
+ ': %d attempts remain' % (Data[20] & 0x0F)
|
||||||
|
fil['unblock_CHV2'] = ('not initialized','initialized')\
|
||||||
|
[(Data[21] & 0x80) / 0x80]\
|
||||||
|
+ ': %d attempts remain' % (Data[21] & 0x0F)
|
||||||
|
if len(Data) > 23:
|
||||||
|
fil['Adm'] = Data[23:]
|
||||||
|
elif fil['Type'] == 'EF':
|
||||||
|
cond = ('ALW', 'CHV1', 'CHV2', 'RFU', 'ADM_4', 'ADM_5',
|
||||||
|
'ADM_6', 'ADM_7', 'ADM_8', 'ADM_9', 'ADM_A',
|
||||||
|
'ADM_B', 'ADM_C', 'ADM_D', 'ADM_E', 'NEW')
|
||||||
|
fil['UPDATE'] = cond[Data[8] & 0x0F]
|
||||||
|
fil['READ'] = cond[Data[8] >> 4]
|
||||||
|
fil['INCREASE'] = cond[Data[9] >> 4]
|
||||||
|
fil['INVALIDATE'] = cond[Data[10] & 0x0F]
|
||||||
|
fil['REHABILITATE'] = cond[Data[10] >> 4]
|
||||||
|
fil['Status'] = ('not read/updatable when invalidated',
|
||||||
|
'read/updatable when invalidated')\
|
||||||
|
[byteToBit(Data[11])[5]] \
|
||||||
|
+ (': invalidated',': not invalidated')\
|
||||||
|
[byteToBit(Data[11])[7]]
|
||||||
|
fil['Structure'] = ('transparent', 'linear fixed', '', 'cyclic')\
|
||||||
|
[Data[13]]
|
||||||
|
if fil['Structure'] == 'cyclic':
|
||||||
|
fil['INCREASE'] = byteToBit(Data[7])[1]
|
||||||
|
if len(Data) > 14:
|
||||||
|
fil['Record Length'] = Data[14]
|
||||||
|
return fil
|
||||||
|
|
||||||
|
def run_gsm_alg(self, RAND=16*[0x00]):
|
||||||
|
'''
|
||||||
|
self.run_gsm_alg( RAND ) -> ( SRES, Kc )
|
||||||
|
RAND : list of bytes, length 16
|
||||||
|
SRES : list of bytes, length 4
|
||||||
|
Kc : list of bytes, length 8
|
||||||
|
|
||||||
|
runs GSM authentication algorithm:
|
||||||
|
accepts any kind of RAND (old GSM fashion)
|
||||||
|
feed with RAND 16 bytes value
|
||||||
|
returns a list with SRES and Kc, or None on error
|
||||||
|
'''
|
||||||
|
if len(RAND) != 16:
|
||||||
|
if self.dbg:
|
||||||
|
log(1, '(run_gsm_alg) bad RAND value: aborting')
|
||||||
|
return None
|
||||||
|
# select DF_GSM directory
|
||||||
|
self.select([0x7F, 0x20])
|
||||||
|
if self.coms()[2] != (0x90, 0x00):
|
||||||
|
if self.dbg >= 2:
|
||||||
|
log(3, '(run_gsm_alg) %s' % self.coms())
|
||||||
|
return None
|
||||||
|
# run authentication
|
||||||
|
self.coms.push(self.INTERNAL_AUTHENTICATE(P1=0x00, P2=0x00, Data=RAND))
|
||||||
|
if self.coms()[2][0] != 0x9F:
|
||||||
|
if self.dbg >= 2:
|
||||||
|
log(3, '(run_gsm_alg) %s' % self.coms())
|
||||||
|
return None
|
||||||
|
# get authentication response
|
||||||
|
self.coms.push(self.GET_RESPONSE(Le=self.coms()[2][1]))
|
||||||
|
if self.coms()[2] != (0x90, 0x00):
|
||||||
|
if self.dbg >= 2:
|
||||||
|
log(3, '(run_gsm_alg) %s' % self.coms())
|
||||||
|
return None
|
||||||
|
SRES, Kc = self.coms()[3][0:4], self.coms()[3][4:]
|
||||||
|
return [ SRES, Kc ]
|
||||||
|
|
||||||
|
def get_imsi(self):
|
||||||
|
'''
|
||||||
|
self.get_imsi() -> string(IMSI)
|
||||||
|
|
||||||
|
reads IMSI value at address [0x6F, 0x07]
|
||||||
|
returns IMSI string on success or None on error
|
||||||
|
'''
|
||||||
|
# select DF_GSM for SIM card
|
||||||
|
self.select([0x7F, 0x20])
|
||||||
|
if self.coms()[2] != (0x90, 0x00):
|
||||||
|
if self.dbg >= 2:
|
||||||
|
log(3, '(get_imsi) %s' % self.coms())
|
||||||
|
return None
|
||||||
|
|
||||||
|
# select IMSI file
|
||||||
|
imsi = self.select([0x6F, 0x07])
|
||||||
|
if self.coms()[2] != (0x90, 0x00):
|
||||||
|
if self.dbg >= 2:
|
||||||
|
log(3, '(get_imsi) %s' % self.coms())
|
||||||
|
return None
|
||||||
|
|
||||||
|
# and parse the received data into the IMSI structure
|
||||||
|
if 'Data' in imsi.keys() and len(imsi['Data']) == 9:
|
||||||
|
return decode_BCD(imsi['Data'])[3:]
|
||||||
|
|
||||||
|
# if issue with the content of the DF_IMSI file
|
||||||
|
if self.dbg >= 2:
|
||||||
|
log(3, '(get_imsi) %s' % self.coms())
|
||||||
|
return None
|
||||||
|
|
||||||
|
def get_services(self):
|
||||||
|
'''
|
||||||
|
self.get_services() -> None
|
||||||
|
|
||||||
|
reads SIM Service Table at address [0x6F, 0x38]
|
||||||
|
returns list of services allowed / activated
|
||||||
|
'''
|
||||||
|
# select DF_GSM for SIM card
|
||||||
|
self.select([0x7F, 0x20])
|
||||||
|
if self.coms()[2] != (0x90, 0x00):
|
||||||
|
if self.dbg >= 2:
|
||||||
|
log(3, '(get_services) %s' % self.coms())
|
||||||
|
return None
|
||||||
|
|
||||||
|
# select SST file
|
||||||
|
sst = self.select([0x6F, 0x38])
|
||||||
|
if self.coms()[2] != (0x90, 0x00):
|
||||||
|
if self.dbg >= 2:
|
||||||
|
log(3, '(get_services) %s' % self.coms())
|
||||||
|
return None
|
||||||
|
|
||||||
|
# parse data and prints corresponding services
|
||||||
|
if 'Data' in sst.keys() and len(sst['Data']) >= 2:
|
||||||
|
return self.get_services_from_sst(sst['Data'])
|
||||||
|
|
||||||
|
def read_services(self):
|
||||||
|
'''
|
||||||
|
self.read_services() -> None
|
||||||
|
|
||||||
|
reads SIM Service Table at address [0x6F, 0x38]
|
||||||
|
prints services allowed / activated
|
||||||
|
returns None
|
||||||
|
'''
|
||||||
|
serv = self.get_services()
|
||||||
|
for s in serv:
|
||||||
|
print(s)
|
||||||
|
|
||||||
|
def get_services_from_sst(self, sst=[0, 0]):
|
||||||
|
services = []
|
||||||
|
cnt = 0
|
||||||
|
for B in sst:
|
||||||
|
# 2 bits per service -> 4 services per byte
|
||||||
|
for i in range(0, 7, 2):
|
||||||
|
cnt += 1
|
||||||
|
if B & 2**i:
|
||||||
|
info = 'allocated'
|
||||||
|
if B & (2**i+1):
|
||||||
|
info += ' | activated'
|
||||||
|
if cnt in SIM_service_table:
|
||||||
|
services.append('%i : %s : %s' \
|
||||||
|
% (cnt, SIM_service_table[cnt], info))
|
||||||
|
else:
|
||||||
|
services.append('%i : %s' % (cnt, info))
|
||||||
|
return services
|
||||||
|
|
||||||
|
def explore_fs(self, filename='sim_fs', depth=True, emul=False):
|
||||||
|
'''
|
||||||
|
self.explore_fs(self, filename='sim_fs') -> void
|
||||||
|
filename: file to write in information found
|
||||||
|
depth: depth in recursivity, True=infinite
|
||||||
|
|
||||||
|
brute force all file addresses from MF recursively
|
||||||
|
(until no more DF are found)
|
||||||
|
write information on existing DF and file in the output file
|
||||||
|
'''
|
||||||
|
simfs_entries = MF_FS.keys()
|
||||||
|
if not emul:
|
||||||
|
self.explore_DF([], None, depth)
|
||||||
|
|
||||||
|
fd = open(filename, 'w')
|
||||||
|
fd.write('\n### MF ###\n')
|
||||||
|
f = self.select()
|
||||||
|
write_dict(f, fd)
|
||||||
|
fd.write('\n')
|
||||||
|
#
|
||||||
|
for f in self.FS:
|
||||||
|
path = tuple(f['Absolut Path'])
|
||||||
|
if path in simfs_entries:
|
||||||
|
f['Name'] = MF_FS[path]
|
||||||
|
write_dict(f, fd)
|
||||||
|
fd.write('\n')
|
||||||
|
|
||||||
|
fd.close()
|
||||||
|
|
||||||
|
def get_ICCID(self):
|
||||||
|
# select MF
|
||||||
|
self.select([0x3F, 0x0])
|
||||||
|
if self.coms()[2] != (0x90, 0x00):
|
||||||
|
if self.dbg >= 2:
|
||||||
|
log(3, '(get_ICCID) %s' % self.coms())
|
||||||
|
return None
|
||||||
|
|
||||||
|
# select IMSI file
|
||||||
|
iccid = self.select([0x2F, 0xE2])
|
||||||
|
if self.coms()[2] != (0x90, 0x00):
|
||||||
|
if self.dbg >= 2:
|
||||||
|
log(3, '(get_ICCID) %s' % self.coms())
|
||||||
|
return None
|
||||||
|
|
||||||
|
# and parse the received data into the IMSI structure
|
||||||
|
if 'Data' in iccid.keys() and len(iccid['Data']) >= 10:
|
||||||
|
return decode_BCD(iccid['Data'])
|
||||||
|
|
||||||
|
# if issue with the content of the ICCID file
|
||||||
|
if self.dbg >= 2:
|
||||||
|
log(3, '(get_ICCID) %s' % self.coms())
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,539 @@
|
||||||
|
# -*- coding: UTF-8 -*-
|
||||||
|
"""
|
||||||
|
card: Library adapted to request (U)SIM cards and other types of telco cards.
|
||||||
|
Copyright (C) 2010 Benoit Michau
|
||||||
|
|
||||||
|
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, write to the Free Software Foundation, Inc.,
|
||||||
|
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
#################################
|
||||||
|
# Python library to work on
|
||||||
|
# USIM card
|
||||||
|
# communication based on ISO7816 card
|
||||||
|
# and commands and formats based on UICC card
|
||||||
|
#
|
||||||
|
# needs pyscard from:
|
||||||
|
# http://pyscard.sourceforge.net/
|
||||||
|
#################################
|
||||||
|
|
||||||
|
from card.ICC import UICC, ISO7816
|
||||||
|
from card.SIM import SIM
|
||||||
|
from card.FS import USIM_app_FS
|
||||||
|
from card.utils import *
|
||||||
|
|
||||||
|
USIM_service_table = {
|
||||||
|
1 : 'Local Phone Book',
|
||||||
|
2 : 'Fixed Dialling Numbers (FDN)',
|
||||||
|
3 : 'Extension 2',
|
||||||
|
4 : 'Service Dialling Numbers (SDN)',
|
||||||
|
5 : 'Extension3',
|
||||||
|
6 : 'Barred Dialling Numbers (BDN)',
|
||||||
|
7 : 'Extension4',
|
||||||
|
8 : 'Outgoing Call Information (OCI and OCT)',
|
||||||
|
9 : 'Incoming Call Information (ICI and ICT)',
|
||||||
|
10 : 'Short Message Storage (SMS)',
|
||||||
|
11 : 'Short Message Status Reports (SMSR)',
|
||||||
|
12 : 'Short Message Service Parameters (SMSP)',
|
||||||
|
13 : 'Advice of Charge (AoC)',
|
||||||
|
14 : 'Capability Configuration Parameters 2 (CCP2)',
|
||||||
|
15 : 'Cell Broadcast Message Identifier ',
|
||||||
|
16 : 'Cell Broadcast Message Identifier Ranges ',
|
||||||
|
17 : 'Group Identifier Level 1',
|
||||||
|
18 : 'Group Identifier Level 2',
|
||||||
|
19 : 'Service Provider Name',
|
||||||
|
20 : 'User controlled PLMN selector with Access Technology',
|
||||||
|
21 : 'MSISDN',
|
||||||
|
22 : 'Image (IMG)',
|
||||||
|
23 : 'Support of Localised Service Areas (SoLSA) ',
|
||||||
|
24 : 'Enhanced Multi-Level Precedence and Pre-emption Service',
|
||||||
|
25 : 'Automatic Answer for eMLPP',
|
||||||
|
26 : 'RFU',
|
||||||
|
27 : 'GSM Access',
|
||||||
|
28 : 'Data download via SMS-PP',
|
||||||
|
29 : 'Data download via SMS-CB',
|
||||||
|
30 : 'Call Control by USIM',
|
||||||
|
31 : 'MO-SMS Control by USIM',
|
||||||
|
32 : 'RUN AT COMMAND command',
|
||||||
|
33 : 'shall be set to \'1\'',
|
||||||
|
34 : 'Enabled Services Table',
|
||||||
|
35 : 'APN Control List (ACL)',
|
||||||
|
36 : 'Depersonalisation Control Keys',
|
||||||
|
37 : 'Co-operative Network List',
|
||||||
|
38 : 'GSM security context ',
|
||||||
|
39 : 'CPBCCH Information',
|
||||||
|
40 : 'Investigation Scan',
|
||||||
|
41 : 'MexE',
|
||||||
|
42 : 'Operator controlled PLMN selector with Access Technology',
|
||||||
|
43 : 'HPLMN selector with Access Technology',
|
||||||
|
44 : 'Extension 5',
|
||||||
|
45 : 'PLMN Network Name',
|
||||||
|
46 : 'Operator PLMN List',
|
||||||
|
47 : 'Mailbox Dialling Numbers ',
|
||||||
|
48 : 'Message Waiting Indication Status',
|
||||||
|
49 : 'Call Forwarding Indication Status',
|
||||||
|
50 : 'Reserved and shall be ignored',
|
||||||
|
51 : 'Service Provider Display Information',
|
||||||
|
52 : 'Multimedia Messaging Service (MMS)',
|
||||||
|
53 : 'Extension 8',
|
||||||
|
54 : 'Call control on GPRS by USIM',
|
||||||
|
55 : 'MMS User Connectivity Parameters',
|
||||||
|
56 : 'Network\'s indication of alerting in the MS (NIA)',
|
||||||
|
57 : 'VGCS Group Identifier List (EFVGCS and EFVGCSS)',
|
||||||
|
58 : 'VBS Group Identifier List (EFVBS and EFVBSS)',
|
||||||
|
59 : 'Pseudonym',
|
||||||
|
60 : 'User Controlled PLMN selector for I-WLAN access',
|
||||||
|
61 : 'Operator Controlled PLMN selector for I-WLAN access',
|
||||||
|
62 : 'User controlled WSID list',
|
||||||
|
63 : 'Operator controlled WSID list',
|
||||||
|
64 : 'VGCS security',
|
||||||
|
65 : 'VBS security',
|
||||||
|
66 : 'WLAN Reauthentication Identity',
|
||||||
|
67 : 'Multimedia Messages Storage',
|
||||||
|
68 : 'Generic Bootstrapping Architecture (GBA)',
|
||||||
|
69 : 'MBMS security',
|
||||||
|
70 : 'Data download via USSD and USSD application mode',
|
||||||
|
71 : 'Equivalent HPLMN',
|
||||||
|
72 : 'Additional TERMINAL PROFILE after UICC activation',
|
||||||
|
73 : 'Equivalent HPLMN Presentation Indication',
|
||||||
|
74 : 'Last RPLMN Selection Indication',
|
||||||
|
75 : 'OMA BCAST Smart Card Profile',
|
||||||
|
76 : 'GBA-based Local Key Establishment Mechanism',
|
||||||
|
77 : 'Terminal Applications',
|
||||||
|
78 : 'Service Provider Name Icon',
|
||||||
|
79 : 'PLMN Network Name Icon',
|
||||||
|
80 : 'Connectivity Parameters for USIM IP connections',
|
||||||
|
81 : 'Home I-WLAN Specific Identifier List',
|
||||||
|
82 : 'I-WLAN Equivalent HPLMN Presentation Indication',
|
||||||
|
83 : 'I-WLAN HPLMN Priority Indication',
|
||||||
|
84 : 'I-WLAN Last Registered PLMN',
|
||||||
|
85 : 'EPS Mobility Management Information',
|
||||||
|
86 : 'Allowed CSG Lists and corresponding indications',
|
||||||
|
87 : 'Call control on EPS PDN connection by USIM',
|
||||||
|
88 : 'HPLMN Direct Access',
|
||||||
|
89 : 'eCall Data',
|
||||||
|
90 : 'Operator CSG Lists and corresponding indications',
|
||||||
|
91 : 'Support for SM-over-IP',
|
||||||
|
92 : 'Support of CSG Display Control',
|
||||||
|
93 : 'Communication Control for IMS by USIM',
|
||||||
|
94 : 'Extended Terminal Applications',
|
||||||
|
95 : 'Support of UICC access to IMS',
|
||||||
|
96 : 'Non-Access Stratum configuration by USIM',
|
||||||
|
97 : 'PWS configuration by USIM',
|
||||||
|
}
|
||||||
|
|
||||||
|
class USIM(UICC):
|
||||||
|
'''
|
||||||
|
defines attributes, methods and facilities for ETSI / 3GPP USIM card
|
||||||
|
check USIM specifications in 3GPP TS 31.102
|
||||||
|
|
||||||
|
inherits (eventually overrides) methods and objects from UICC class
|
||||||
|
use self.dbg = 1 or more to print live debugging information
|
||||||
|
'''
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
'''
|
||||||
|
initializes like an ISO7816-4 card with CLA=0x00
|
||||||
|
and checks available AID (Application ID) read from EF_DIR
|
||||||
|
|
||||||
|
initializes on the MF
|
||||||
|
'''
|
||||||
|
# initialize like a UICC
|
||||||
|
ISO7816.__init__(self, CLA=0x00)
|
||||||
|
self.AID = []
|
||||||
|
|
||||||
|
if self.dbg >= 2:
|
||||||
|
log(3, '(UICC.__init__) type definition: %s' % type(self))
|
||||||
|
log(3, '(UICC.__init__) CLA definition: %s' % hex(self.CLA))
|
||||||
|
|
||||||
|
self.SELECT_ADF_USIM()
|
||||||
|
|
||||||
|
def SELECT_ADF_USIM(self):
|
||||||
|
# USIM selection from AID
|
||||||
|
if self.dbg:
|
||||||
|
log(3, '(USIM.__init__) UICC AID found:')
|
||||||
|
self.get_AID()
|
||||||
|
for aid in self.AID:
|
||||||
|
if tuple(aid[0:5]) == (0xA0, 0x00, 0x00, 0x00, 0x87) \
|
||||||
|
and tuple(aid[5:7]) == (0x10, 0x02) :
|
||||||
|
usim = self.select(addr=aid, type='aid')
|
||||||
|
if usim is None and self.dbg:
|
||||||
|
log(2, '(USIM.__init__) USIM AID selection failed')
|
||||||
|
if usim is not None:
|
||||||
|
self.USIM_AID = aid
|
||||||
|
if self.dbg:
|
||||||
|
log(3, '(USIM.__init__) USIM AID selection succeeded\n')
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def sw_status(sw1, sw2):
|
||||||
|
status = SIM.sw_status(sw1, sw2)
|
||||||
|
if sw1 == 0x98 and sw2 in (0x62, 0x64, 0x65, 0x66, 0x67):
|
||||||
|
status = 'security management'
|
||||||
|
if sw2 == 0x62: status += ': authentication error, ' \
|
||||||
|
'incorrect MAC'
|
||||||
|
elif sw2 == 0x64: status += ': authentication error, ' \
|
||||||
|
'security context not supported'
|
||||||
|
elif sw2 == 0x65: status += ': key freshness failure'
|
||||||
|
elif sw2 == 0x66: status += ': authentication error, ' \
|
||||||
|
'no memory space available'
|
||||||
|
elif sw2 == 0x67: status += ': authentication error, ' \
|
||||||
|
'no memory space available in EF_MUK'
|
||||||
|
return status
|
||||||
|
|
||||||
|
def get_imsi(self):
|
||||||
|
'''
|
||||||
|
get_imsi() -> string(IMSI)
|
||||||
|
|
||||||
|
reads IMSI value at address [0x6F, 0x07]
|
||||||
|
returns IMSI string on success or None on error
|
||||||
|
'''
|
||||||
|
# select IMSI file
|
||||||
|
imsi = self.select([0x6F, 0x07])
|
||||||
|
if imsi is None:
|
||||||
|
return None
|
||||||
|
# and parse the received data into the IMSI structure
|
||||||
|
if 'Data' in imsi.keys() and len(imsi['Data']) == 9:
|
||||||
|
return decode_BCD(imsi['Data'])[3:]
|
||||||
|
|
||||||
|
# if issue with the content of the DF_IMSI file
|
||||||
|
if self.dbg >= 2:
|
||||||
|
log(3, '(get_imsi) %s' % self.coms())
|
||||||
|
return None
|
||||||
|
|
||||||
|
def get_CS_keys(self):
|
||||||
|
'''
|
||||||
|
get_CS_keys() -> [KSI, CK, IK]
|
||||||
|
|
||||||
|
reads CS UMTS keys at address [0x6F, 0x08]
|
||||||
|
returns list of 3 keys, each are list of bytes, on success
|
||||||
|
(or eventually the whole file dict if the format is strange)
|
||||||
|
or None on error
|
||||||
|
'''
|
||||||
|
EF_KEYS = self.select( [0x6F, 0x08] )
|
||||||
|
if self.coms()[2] == (0x90, 0x00):
|
||||||
|
if len(EF_KEYS['Data']) == 33:
|
||||||
|
KSI, CK, IK = ( EF_KEYS['Data'][0:1],
|
||||||
|
EF_KEYS['Data'][1:17],
|
||||||
|
EF_KEYS['Data'][17:33])
|
||||||
|
log(3, '(get_CS_keys) successful CS keys selection: ' \
|
||||||
|
'Get [KSI, CK, IK]')
|
||||||
|
return [KSI, CK, IK]
|
||||||
|
else:
|
||||||
|
return EF_KEYS
|
||||||
|
return None
|
||||||
|
|
||||||
|
def get_PS_keys(self):
|
||||||
|
'''
|
||||||
|
get_PS_keys() -> [KSI, CK_PS, IK_PS]
|
||||||
|
|
||||||
|
reads PS UMTS keys at address [0x6F, 0x09]
|
||||||
|
returns list of 3 keys, each are list of bytes, on success
|
||||||
|
(or eventually the whole file dict if the format is strange)
|
||||||
|
or None on error
|
||||||
|
'''
|
||||||
|
EF_KEYSPS = self.select( [0x6F, 0x09] )
|
||||||
|
if self.coms()[2] == (0x90, 0x00):
|
||||||
|
if len(EF_KEYSPS['Data']) == 33:
|
||||||
|
KSI, CK, IK = ( EF_KEYSPS['Data'][0:1],
|
||||||
|
EF_KEYSPS['Data'][1:17],
|
||||||
|
EF_KEYSPS['Data'][17:33] )
|
||||||
|
log(3, '(get_PS_keys) successful PS keys selection: ' \
|
||||||
|
'Get [KSI, CK, IK]')
|
||||||
|
return [KSI, CK, IK]
|
||||||
|
else:
|
||||||
|
return EF_KEYSPS
|
||||||
|
return None
|
||||||
|
|
||||||
|
def get_GBA_BP(self):
|
||||||
|
'''
|
||||||
|
get_GBA_BP() -> [[RAND, B-TID, KeyLifetime], ...],
|
||||||
|
Length-Value parsing style
|
||||||
|
|
||||||
|
reads EF_GBABP file at address [0x6F, 0xD6],
|
||||||
|
containing RAND and associated B-TID and KeyLifetime
|
||||||
|
returns list of list of bytes on success
|
||||||
|
(or eventually the whole file dict if the format is strange)
|
||||||
|
or None on error
|
||||||
|
'''
|
||||||
|
EF_GBABP = self.select( [0x6F, 0xD6] )
|
||||||
|
if self.coms()[2] == (0x90, 0x00):
|
||||||
|
if len(EF_GBABP['Data']) > 2:
|
||||||
|
#RAND, B_TID, Lifetime = LV_parser( EF_GBABP['Data'] )
|
||||||
|
log(3, '(get_GBA_BP) successful GBA_BP selection: ' \
|
||||||
|
'Get list of [RAND, B-TID, KeyLifetime]')
|
||||||
|
#return (RAND, B_TID, Lifetime)
|
||||||
|
return LV_parser( EF_GBABP['Data'] )
|
||||||
|
else:
|
||||||
|
return EF_GBABP
|
||||||
|
return None
|
||||||
|
|
||||||
|
def update_GBA_BP(self, RAND, B_TID, key_lifetime):
|
||||||
|
'''
|
||||||
|
update_GBA_BP([RAND], [B_TID], [key_lifetime])
|
||||||
|
-> void (or EF_GBABP file dict if RAND not found)
|
||||||
|
|
||||||
|
reads EF_GBABP file at address [0x6F, 0xD6],
|
||||||
|
checks if RAND provided is referenced,
|
||||||
|
and updates the file structure with provided B-TID and KeyLifetime
|
||||||
|
returns nothing (or eventually the whole file dict
|
||||||
|
if the RAND is not found)
|
||||||
|
'''
|
||||||
|
GBA_BP = self.get_GBA_BP()
|
||||||
|
for i in GBA_BP:
|
||||||
|
if i == RAND:
|
||||||
|
log(3, '(update_GBA_BP) RAND found in GBA_BP')
|
||||||
|
# update transparent file with B_TID and key lifetime
|
||||||
|
self.coms.push( self.UPDATE_BINARY( P2=len(RAND)+1,
|
||||||
|
Data=[len(B_TID)] + B_TID + \
|
||||||
|
[len(key_lifetime)] + key_lifetime ))
|
||||||
|
if self.dbg >= 2:
|
||||||
|
log(3, '(update_GBA_BP) %s' % self.coms())
|
||||||
|
if self.coms()[2] == 0x90 and self.dbg:
|
||||||
|
log(3, '(update_GBA_BP) successful GBA_BP update with ' \
|
||||||
|
'B-TID and key lifetime')
|
||||||
|
if self.dbg >= 3:
|
||||||
|
log(3, '(update_GBA_BP) new value of EF_GBA_BP:\n%s' \
|
||||||
|
% self.get_GBA_BP())
|
||||||
|
else:
|
||||||
|
if self.dbg:
|
||||||
|
log(2, '(update_GBA_BP) RAND not found in GBA_BP')
|
||||||
|
return GBA_BP
|
||||||
|
|
||||||
|
def get_GBA_NL(self):
|
||||||
|
'''
|
||||||
|
get_GBA_NL() -> [[NAF_ID, B-TID], ...] , TLV parsing style
|
||||||
|
|
||||||
|
reads EF_GBANL file at address [0x6F, 0xDA], containing NAF_ID and B-TID
|
||||||
|
returns list of list of bytes vector on success
|
||||||
|
(or eventually the whole file dict if the format is strange)
|
||||||
|
or None on error
|
||||||
|
'''
|
||||||
|
EF_GBANL = self.select( [0x6F, 0xDA] )
|
||||||
|
if self.coms()[2] == (0x90, 0x00):
|
||||||
|
if len(EF_GBANL['Data'][0]) > 2:
|
||||||
|
# This is Tag-Length-Value parsing,
|
||||||
|
# with 0x80 for NAF_ID and 0x81 for B-TID
|
||||||
|
values = []
|
||||||
|
|
||||||
|
for rec in EF_GBANL['Data']:
|
||||||
|
NAF_ID, B_TID = [], []
|
||||||
|
while len(rec) > 0:
|
||||||
|
tlv = first_TLV_parser( rec )
|
||||||
|
if tlv[1] > 0xFF:
|
||||||
|
rec = rec[ tlv[1]+4 : ]
|
||||||
|
else:
|
||||||
|
rec = rec[ tlv[1]+2 : ]
|
||||||
|
if tlv[0] == 0x80:
|
||||||
|
NAF_ID = tlv[2]
|
||||||
|
elif tlv[0] == 0x81:
|
||||||
|
B_TID = tlv[2]
|
||||||
|
values.append( [NAF_ID, B_TID] )
|
||||||
|
|
||||||
|
log(3, '(get_GBA_NL) Successful GBA_NL selection: ' \
|
||||||
|
'Get list of [NAF_ID, B-TID]')
|
||||||
|
#return (NAF_ID, B_TID)
|
||||||
|
return values
|
||||||
|
else:
|
||||||
|
return EF_GBANL
|
||||||
|
return None
|
||||||
|
|
||||||
|
def authenticate(self, RAND=[], AUTN=[], ctx='3G'):
|
||||||
|
'''
|
||||||
|
self.authenticate(RAND, AUTN, ctx='3G') -> [key1, key2...],
|
||||||
|
LV parsing style
|
||||||
|
|
||||||
|
runs the INTERNAL AUTHENTICATE command in the USIM
|
||||||
|
with the right context:
|
||||||
|
ctx = '2G', '3G', 'GBA' ('MBMS' or other not supported at this time)
|
||||||
|
RAND and AUTN are list of bytes; for '2G' context, AUTN is not used
|
||||||
|
returns a list containing the keys (list of bytes) computed in the USIM,
|
||||||
|
on success:
|
||||||
|
[RES, CK, IK (, Kc)] or [AUTS] for '3G'
|
||||||
|
[RES] or [AUTS] for 'GBA'
|
||||||
|
[RES, Kc] for '2G'
|
||||||
|
or None on error
|
||||||
|
'''
|
||||||
|
# prepare input data for authentication
|
||||||
|
if ctx in ('3G', 'VGCS', 'GBA', 'MBMS') and len(RAND) != 16 \
|
||||||
|
and len(AUTN) != 16:
|
||||||
|
log(1, '(authenticate) bad AUTN parameter: aborting')
|
||||||
|
return None
|
||||||
|
|
||||||
|
inp = []
|
||||||
|
if ctx == '3G':
|
||||||
|
P2 = 0x81
|
||||||
|
elif ctx == 'VGCS':
|
||||||
|
P2 = 0x82
|
||||||
|
log(1, '(authenticate) VGCS auth not implemented: aborting')
|
||||||
|
return None
|
||||||
|
elif ctx == 'MBMS':
|
||||||
|
log(1, '(authenticate) MBMS auth not implemented: aborting')
|
||||||
|
return None
|
||||||
|
elif ctx == 'GBA':
|
||||||
|
P2 = 0x84
|
||||||
|
inp = [0xDD]
|
||||||
|
inp.extend( [len(RAND)] + RAND + [len(AUTN)] + AUTN )
|
||||||
|
if ctx not in ['3G', 'VGCS', 'MBMS', 'GBA']:
|
||||||
|
# and also, if ctx == '2G'... the safe way
|
||||||
|
# to avoid desynchronizing our USIM counter
|
||||||
|
P2 = 0x80
|
||||||
|
if len(RAND) != 16:
|
||||||
|
log(1, '(authenticate) bad RAND parameter: aborting')
|
||||||
|
return None
|
||||||
|
# override input value for 2G authent
|
||||||
|
inp = [len(RAND)] + RAND
|
||||||
|
|
||||||
|
self.coms.push( self.INTERNAL_AUTHENTICATE(P2=P2, Data=inp) )
|
||||||
|
if self.coms()[2][0] in (0x9F, 0x61):
|
||||||
|
self.coms.push( self.GET_RESPONSE(Le=self.coms()[2][1]) )
|
||||||
|
if self.coms()[2] == (0x90, 0x00):
|
||||||
|
val = self.coms()[3]
|
||||||
|
if P2 == 0x80:
|
||||||
|
if self.dbg:
|
||||||
|
log(3, '(authenticate) successful 2G authentication. ' \
|
||||||
|
'Get [RES, Kc]')
|
||||||
|
values = LV_parser(val)
|
||||||
|
# returned values are (RES, Kc)
|
||||||
|
return values
|
||||||
|
# not adapted to 2G context with Kc, RES: to be confirmed...
|
||||||
|
if val[0] == 0xDB:
|
||||||
|
if P2 == 0x81 and self.dbg:
|
||||||
|
log(3, '(authenticate) successful 3G authentication. ' \
|
||||||
|
'Get [RES, CK, IK(, Kc)]')
|
||||||
|
elif P2 == 0x84 and self.dbg:
|
||||||
|
log(3, '(authenticate) successful GBA authentication.' \
|
||||||
|
' Get [RES]')
|
||||||
|
values = LV_parser(val[1:])
|
||||||
|
# returned values can be (RES, CK, IK) or (RES, CK, IK, Kc)
|
||||||
|
return values
|
||||||
|
elif val[0] == 0xDC:
|
||||||
|
if self.dbg:
|
||||||
|
log(2, '(authenticate) synchronization failure. ' \
|
||||||
|
'Get [AUTS]')
|
||||||
|
values = LV_parser(val[1:])
|
||||||
|
return values
|
||||||
|
#else:
|
||||||
|
if self.dbg:
|
||||||
|
log(1, '(authenticate) error: %s' % self.coms())
|
||||||
|
return None
|
||||||
|
|
||||||
|
def GBA_derivation(self, NAF_ID=[], IMPI=[]):
|
||||||
|
'''
|
||||||
|
self.GBA_derivation(NAF_ID, IMPI) -> [Ks_ext_naf]
|
||||||
|
|
||||||
|
runs the INTERNAL AUTHENTICATE command in the USIM
|
||||||
|
with the GBA derivation context:
|
||||||
|
NAF_ID is a list of bytes (use stringToByte())
|
||||||
|
"NAF domain name"||"security protocol id",
|
||||||
|
eg: "application.org"||"0x010001000a" (> TLS with RSA and SHA)
|
||||||
|
IMPI is a list of bytes
|
||||||
|
"IMSI@ims.mncXXX.mccYYY.3gppnetwork.org" if no IMS IMPI
|
||||||
|
is specifically defined in the USIM
|
||||||
|
returns a list with GBA ext key (list of bytes) computed in the USIM:
|
||||||
|
[Ks_ext_naf]
|
||||||
|
Ks_int_naf remains available in the USIM
|
||||||
|
for further GBA_U key derivation
|
||||||
|
or None on error
|
||||||
|
|
||||||
|
see TS 33.220 for GBA specific formats
|
||||||
|
'''
|
||||||
|
# need to run 1st an authenicate command with 'GBA' context,
|
||||||
|
# so to have the required keys in the USIM
|
||||||
|
P2 = 0x84
|
||||||
|
inp = [0xDE] + [len(NAF_ID)] + NAF_ID + [len(IMPI)] + IMPI
|
||||||
|
|
||||||
|
self.coms.push( self.INTERNAL_AUTHENTICATE(P2=P2, Data=inp) )
|
||||||
|
if self.coms()[2][0] in (0x9F, 0x61):
|
||||||
|
self.coms.push( self.GET_RESPONSE(Le=self.coms()[2][1]) )
|
||||||
|
if self.coms()[2] == (0x90, 0x00):
|
||||||
|
val = self.coms()[3]
|
||||||
|
if val[0] == 0xDB: # not adapted to 2G context with Kc, RES
|
||||||
|
if self.dbg:
|
||||||
|
log(3, '(GBA_derivation) successful GBA derivation. ' \
|
||||||
|
'Get [Ks_EXT_NAF]')
|
||||||
|
values = LV_parser(val[1:])
|
||||||
|
return values
|
||||||
|
if self.dbg:
|
||||||
|
log(3, '(GBA_derivation) authentication failure: %s' % self.coms())
|
||||||
|
return None
|
||||||
|
|
||||||
|
def get_services(self):
|
||||||
|
'''
|
||||||
|
self.get_services() -> None
|
||||||
|
|
||||||
|
reads USIM Service Table at address [0x6F, 0x38]
|
||||||
|
prints services allowed / activated
|
||||||
|
returns None
|
||||||
|
'''
|
||||||
|
# select SST file
|
||||||
|
sst = self.select([0x6F, 0x38])
|
||||||
|
if self.coms()[2] != (0x90, 0x00):
|
||||||
|
if self.dbg >= 2:
|
||||||
|
log(3, '(get_services) %s' % self.coms())
|
||||||
|
return None
|
||||||
|
|
||||||
|
# parse data and prints corresponding services
|
||||||
|
if 'Data' in sst.keys() and len(sst['Data']) >= 2:
|
||||||
|
return self.get_services_from_sst(sst['Data'])
|
||||||
|
|
||||||
|
def read_services(self):
|
||||||
|
serv = self.get_services()
|
||||||
|
for s in serv:
|
||||||
|
print(s)
|
||||||
|
|
||||||
|
def get_services_from_sst(self, sst=[0, 0]):
|
||||||
|
services = []
|
||||||
|
cnt = 0
|
||||||
|
for B in sst:
|
||||||
|
# 1 bit per service -> 8 services per byte
|
||||||
|
for i in range(0, 8):
|
||||||
|
cnt += 1
|
||||||
|
if B & 2**i:
|
||||||
|
if cnt in USIM_service_table:
|
||||||
|
services.append('%i : %s : available' \
|
||||||
|
% (cnt, USIM_service_table[cnt]))
|
||||||
|
else:
|
||||||
|
services.append('%i : available' % cnt)
|
||||||
|
return services
|
||||||
|
|
||||||
|
def explore_fs(self, filename='usim_fs', depth=2):
|
||||||
|
'''
|
||||||
|
self.explore_fs(self, filename='usim_fs') -> void
|
||||||
|
filename: file to write in information found
|
||||||
|
depth: depth in recursivity, True=infinite
|
||||||
|
|
||||||
|
brute force all file addresses from 1st USIM AID
|
||||||
|
with a maximum recursion level (to avoid infinite looping...)
|
||||||
|
write information on existing DF and file in the output file
|
||||||
|
'''
|
||||||
|
usimfs_entries = USIM_app_FS.keys()
|
||||||
|
self.explore_DF([], self.AID.index(self.USIM_AID)+1, depth)
|
||||||
|
|
||||||
|
fd = open(filename, 'w')
|
||||||
|
fd.write('\n### AID %s ###\n' % self.USIM_AID)
|
||||||
|
f = self.select_by_aid( self.AID.index(self.USIM_AID)+1 )
|
||||||
|
write_dict(f, fd)
|
||||||
|
fd.write('\n')
|
||||||
|
#
|
||||||
|
for f in self.FS:
|
||||||
|
path = tuple(f['Absolut Path'])
|
||||||
|
if path in usimfs_entries:
|
||||||
|
f['Name'] = USIM_app_FS[path]
|
||||||
|
write_dict(f, fd)
|
||||||
|
fd.write('\n')
|
||||||
|
|
||||||
|
fd.close()
|
||||||
|
#
|
||||||
|
|
|
@ -0,0 +1,27 @@
|
||||||
|
# -*- coding: UTF-8 -*-
|
||||||
|
"""
|
||||||
|
card: Library adapted to request (U)SIM cards and other types of telco cards.
|
||||||
|
Copyright (C) 2010 Benoit Michau
|
||||||
|
|
||||||
|
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, write to the Free Software Foundation, Inc.,
|
||||||
|
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# smartcard Integrated Circuit Card library
|
||||||
|
# based on Laurent Rousseau pcsclite daemon or Microsoft scard service
|
||||||
|
# and Jean-Daniel Aussel pyscard (magical) python binding
|
||||||
|
# specificities of SIM and USIM card available
|
||||||
|
|
||||||
|
__all__ = ['utils', 'ICC', 'SIM', 'USIM', 'FS']
|
||||||
|
__version__ = '0.2.0'
|
|
@ -0,0 +1,334 @@
|
||||||
|
# -*- coding: UTF-8 -*-
|
||||||
|
"""
|
||||||
|
card: Library adapted to request (U)SIM cards and other types of telco cards.
|
||||||
|
Copyright (C) 2010 Benoit Michau
|
||||||
|
|
||||||
|
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, write to the Free Software Foundation, Inc.,
|
||||||
|
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||||
|
"""
|
||||||
|
|
||||||
|
#################################
|
||||||
|
# generic functions #
|
||||||
|
# being used in smartcard specs #
|
||||||
|
#################################
|
||||||
|
|
||||||
|
from collections import deque
|
||||||
|
from smartcard.util import toBytes
|
||||||
|
|
||||||
|
|
||||||
|
###############
|
||||||
|
# log wrapper #
|
||||||
|
###############
|
||||||
|
log_levels = {1:'ERR', 2:'WNG', 3:'DBG'}
|
||||||
|
def log(level, string):
|
||||||
|
# could output to a file
|
||||||
|
# but here, just print()
|
||||||
|
print('[%s] %s' % (log_levels[level], string))
|
||||||
|
|
||||||
|
# from python 2.6, format('b') allows to use 0b10010110 notation:
|
||||||
|
# much convinient
|
||||||
|
def byteToBit(byte):
|
||||||
|
'''
|
||||||
|
byteToBit(0xAB) -> [1, 0, 1, 0, 1, 0, 1, 1]
|
||||||
|
|
||||||
|
converts a byte integer value into a list of bits
|
||||||
|
'''
|
||||||
|
bit = [0, 0, 0, 0, 0, 0, 0, 0]
|
||||||
|
for i in range(8):
|
||||||
|
if byte % pow(2, i+1):
|
||||||
|
bit[7-i] = 1
|
||||||
|
byte = byte - pow(2, i)
|
||||||
|
return bit
|
||||||
|
|
||||||
|
# equivalent to the pyscard function "toASCIIBytes"
|
||||||
|
# new version of python (>2.6) seems to have a built-in "bytes" type
|
||||||
|
def stringToByte(string):
|
||||||
|
'''
|
||||||
|
stringToByte('test') -> [116, 101, 115, 116]
|
||||||
|
|
||||||
|
converts a string into a list of bytes
|
||||||
|
'''
|
||||||
|
bytelist = []
|
||||||
|
for c in string:
|
||||||
|
bytelist.extend( toBytes(c.encode('hex')) )
|
||||||
|
return bytelist
|
||||||
|
|
||||||
|
# equivalent to the pyscard function "toASCIIString"
|
||||||
|
def byteToString(bytelist):
|
||||||
|
'''
|
||||||
|
byteToString([116, 101, 115, 116]) -> 'test'
|
||||||
|
|
||||||
|
converts a list of bytes into a string
|
||||||
|
'''
|
||||||
|
string = ''
|
||||||
|
for b in bytelist:
|
||||||
|
string += chr(b)
|
||||||
|
return string
|
||||||
|
|
||||||
|
def LV_parser(bytelist):
|
||||||
|
'''
|
||||||
|
LV_parser([0x02, 0xAB, 0xCD, 0x01, 0x12, 0x34]) -> [[171, 205], [18], []]
|
||||||
|
|
||||||
|
parses Length-Value records in a list of bytes
|
||||||
|
returns a list of list of bytes
|
||||||
|
length coded on 1 byte
|
||||||
|
'''
|
||||||
|
values = []
|
||||||
|
while len(bytelist) > 0:
|
||||||
|
l = bytelist[0]
|
||||||
|
values.append( bytelist[1:1+l] )
|
||||||
|
bytelist = bytelist[1+l:]
|
||||||
|
return values
|
||||||
|
|
||||||
|
def first_TLV_parser(bytelist):
|
||||||
|
'''
|
||||||
|
first_TLV_parser([0xAA, 0x02, 0xAB, 0xCD, 0xFF, 0x00]) -> (170, 2, [171, 205])
|
||||||
|
|
||||||
|
parses first TLV format record in a list of bytelist
|
||||||
|
returns a 3-Tuple: Tag, Length, Value
|
||||||
|
Value is a list of bytes
|
||||||
|
parsing of length is ETSI'style 101.220
|
||||||
|
'''
|
||||||
|
Tag = bytelist[0]
|
||||||
|
if bytelist[1] == 0xFF:
|
||||||
|
Len = bytelist[2]*256 + bytelist[3]
|
||||||
|
Val = bytelist[4:4+Len]
|
||||||
|
else:
|
||||||
|
Len = bytelist[1]
|
||||||
|
Val = bytelist[2:2+Len]
|
||||||
|
return (Tag, Len, Val)
|
||||||
|
|
||||||
|
def TLV_parser(bytelist):
|
||||||
|
'''
|
||||||
|
TLV_parser([0xAA, ..., 0xFF]) -> [(T, L, [V]), (T, L, [V]), ...]
|
||||||
|
|
||||||
|
loops on the input list of bytes with the "first_TLV_parser()" function
|
||||||
|
returns a list of 3-Tuples
|
||||||
|
'''
|
||||||
|
ret = []
|
||||||
|
while len(bytelist) > 0:
|
||||||
|
T, L, V = first_TLV_parser(bytelist)
|
||||||
|
if T == 0xFF:
|
||||||
|
# padding bytes
|
||||||
|
break
|
||||||
|
ret.append( (T, L, V) )
|
||||||
|
# need to manage length of L
|
||||||
|
if L > 0xFE:
|
||||||
|
bytelist = bytelist[ L+4 : ]
|
||||||
|
else:
|
||||||
|
bytelist = bytelist[ L+2 : ]
|
||||||
|
return ret
|
||||||
|
|
||||||
|
def first_BERTLV_parser(bytelist):
|
||||||
|
'''
|
||||||
|
first_BERTLV_parser([0xAA, 0x02, 0xAB, 0xCD, 0xFF, 0x00])
|
||||||
|
-> ([1, 'contextual', 'constructed', 10], [1, 2], [171, 205])
|
||||||
|
|
||||||
|
parses first BER-TLV format record in a list of bytes
|
||||||
|
returns a 3-Tuple: Tag, Length, Value
|
||||||
|
Tag: [Tag class, Tag DO, Tag number]
|
||||||
|
Length: [Length of length, Length value]
|
||||||
|
Value: [Value bytes list]
|
||||||
|
parsing of length is ETSI'style 101.220
|
||||||
|
'''
|
||||||
|
# Tag class and DO
|
||||||
|
byte0 = byteToBit(bytelist[0])
|
||||||
|
if byte0[0:2] == [0, 0]:
|
||||||
|
Tag_class = 'universal'
|
||||||
|
elif byte0[0:2] == [0, 1]:
|
||||||
|
Tag_class = 'applicative'
|
||||||
|
elif byte0[0:2] == [1, 0]:
|
||||||
|
Tag_class = 'contextual'
|
||||||
|
elif byte0[0:2] == [1, 1]:
|
||||||
|
Tag_class = 'private'
|
||||||
|
if byte0[2:3] == [0]:
|
||||||
|
Tag_DO = 'primitive'
|
||||||
|
elif byte0[2:3] == [1]:
|
||||||
|
Tag_DO = 'constructed'
|
||||||
|
# Tag coded with more than 1 byte
|
||||||
|
i = 0
|
||||||
|
if byte0[3:8] == [1, 1, 1, 1, 1]:
|
||||||
|
Tag_bits = byteToBit(bytelist[1])[1:8]
|
||||||
|
i += 1
|
||||||
|
while byteToBit(bytelist[i])[0] == 1:
|
||||||
|
i += 1
|
||||||
|
Tag_bits += byteToBit(bytelist[i])[1:8]
|
||||||
|
# Tag coded with 1 byte
|
||||||
|
else:
|
||||||
|
Tag_bits = byte0[3:8]
|
||||||
|
|
||||||
|
# Tag number calculation
|
||||||
|
Tag_num = 0
|
||||||
|
for j in range(len(Tag_bits)):
|
||||||
|
Tag_num += Tag_bits[len(Tag_bits)-j-1] * pow(2, j)
|
||||||
|
|
||||||
|
# Length coded with more than 1 byte (BER long form)
|
||||||
|
if bytelist[i+1] & 0x80:
|
||||||
|
Len_num = bytelist[i+1] - 0x80
|
||||||
|
Len = reduce(lambda x,y: (x<<8)+y, bytelist[i+2:i+2+Len_num])
|
||||||
|
Val = bytelist[i+2+Len_num:i+2+Len_num+Len]
|
||||||
|
# Length coded with 1 byte
|
||||||
|
else:
|
||||||
|
Len_num = 1
|
||||||
|
Len = bytelist[i+1]
|
||||||
|
Val = bytelist[i+2:i+2+Len]
|
||||||
|
|
||||||
|
return ([i+1, Tag_class, Tag_DO, Tag_num], [Len_num, Len], Val)
|
||||||
|
#return ([Tag_class, Tag_DO, Tag_num], Len, Val)
|
||||||
|
|
||||||
|
def BERTLV_parser(bytelist):
|
||||||
|
'''
|
||||||
|
BERTLV_parser([0xAA, ..., 0xFF]) -> [([T], L, [V]), ([T], L, [V]), ...]
|
||||||
|
|
||||||
|
loops on the input bytes with the "first_BERTLV_parser()" function
|
||||||
|
returns a list of 3-Tuples containing BERTLV records
|
||||||
|
'''
|
||||||
|
ret = []
|
||||||
|
while len(bytelist) > 0:
|
||||||
|
T, L, V = first_BERTLV_parser(bytelist)
|
||||||
|
#if T == 0xFF:
|
||||||
|
# break # padding bytes
|
||||||
|
ret.append( (T[1:], L[1], V) )
|
||||||
|
# need to manage lengths of Tag and Length
|
||||||
|
bytelist = bytelist[ T[0] + L[0] + L[1] : ]
|
||||||
|
return ret
|
||||||
|
|
||||||
|
def decode_BCD(data=[]):
|
||||||
|
'''
|
||||||
|
decode_BCD([0x21, 0xFE, 0xA3]) -> '121415310'
|
||||||
|
|
||||||
|
to decode serial number (IMSI, ICCID...) from list of bytes
|
||||||
|
'''
|
||||||
|
string = ''
|
||||||
|
for B in data:
|
||||||
|
# 1st digit (4 LSB), can be padding (e.g. 0xF)
|
||||||
|
if (B&0x0F) < 10: string += str(B&0x0F)
|
||||||
|
# 2nd digit (4 MSB), can be padding (e.g. 0xF)
|
||||||
|
if (B>>4) < 10: string += str(B>>4)
|
||||||
|
return string
|
||||||
|
|
||||||
|
def compute_luhn(digit_str=''):
|
||||||
|
'''
|
||||||
|
compute_luhn('15632458') -> 4
|
||||||
|
|
||||||
|
return the luhn code of the digits provided
|
||||||
|
'''
|
||||||
|
if not digit_str.isdigit():
|
||||||
|
print('you must provide a string of digits')
|
||||||
|
return
|
||||||
|
# append 0
|
||||||
|
d = [int(c) for c in digit_str+'0']
|
||||||
|
# sum of odd digits
|
||||||
|
cs = sum(d[-1::-2])
|
||||||
|
# sum of (sum of digits(even digits * 2))
|
||||||
|
cs += sum([(v*2)%9 if v != 9 else 9 for v in d[-2::-2]])
|
||||||
|
# modulo 10: luhn checksum
|
||||||
|
cs = cs%10
|
||||||
|
# return the luhn code
|
||||||
|
if cs == 0: return cs
|
||||||
|
else: return 10-cs
|
||||||
|
|
||||||
|
def write_dict(dict, fd):
|
||||||
|
'''
|
||||||
|
write a dict() content to a file descriptor
|
||||||
|
'''
|
||||||
|
keys = dict.keys()
|
||||||
|
keys.sort()
|
||||||
|
fd.write('\n')
|
||||||
|
for k in keys:
|
||||||
|
rec = dict[k]
|
||||||
|
if isinstance(rec, list) and \
|
||||||
|
len(rec) == [isinstance(i, int) for i in rec].count(True):
|
||||||
|
rec = ''.join(['[', ', '.join(map(hex, rec)), ']'])
|
||||||
|
fd.write('%s: %s\n' % (k, rec))
|
||||||
|
|
||||||
|
def make_graph(FS, master_name='(0x3F, 0x00)\nMF'):
|
||||||
|
try:
|
||||||
|
import pydot
|
||||||
|
except:
|
||||||
|
log(1, '(make_graph) pydot library not found: aborting')
|
||||||
|
return
|
||||||
|
#return
|
||||||
|
# build a graph using graphviz Dot language, with pydot
|
||||||
|
# create a graph with master MF or AID node
|
||||||
|
nodes={}
|
||||||
|
graph = pydot.Dot(graph_type='digraph', rankdir='LR')
|
||||||
|
nodes['master'] = pydot.Node(master_name, style='filled', fillcolor='green')
|
||||||
|
graph.add_node(nodes['master'])
|
||||||
|
# attach nodes under master
|
||||||
|
for file in FS:
|
||||||
|
abspath = file['Absolut Path']
|
||||||
|
# build file name for the node
|
||||||
|
label = ''.join(( \
|
||||||
|
''.join((file['Name'], '\n')) if 'Name' in file.keys() else '', \
|
||||||
|
'(', hex(abspath[-2]), ' ', hex(abspath[-1]), ')'))
|
||||||
|
# check for EF or DF for node color
|
||||||
|
color='yellow' if file['Type'][:2] == 'EF' else 'blue'
|
||||||
|
# make node
|
||||||
|
nodes['%s'%abspath] = pydot.Node('%s'%abspath, label=label,\
|
||||||
|
style='filled', fillcolor=color)
|
||||||
|
# put it in the graph and append it to the parent
|
||||||
|
graph.add_node(nodes['%s'%abspath])
|
||||||
|
graph.add_edge(pydot.Edge(nodes['%s'%abspath[:-2]] if \
|
||||||
|
len(abspath) >= 4 else nodes['master'], \
|
||||||
|
nodes['%s'%abspath]))
|
||||||
|
# and return graph ...
|
||||||
|
return graph
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#######################################################
|
||||||
|
# Generic class to keep track of sent / received APDU #
|
||||||
|
#######################################################
|
||||||
|
class apdu_stack:
|
||||||
|
'''
|
||||||
|
input / output wrapping class
|
||||||
|
for APDU communications
|
||||||
|
|
||||||
|
allows to keep track of communications
|
||||||
|
and exchanged commands
|
||||||
|
|
||||||
|
based on the python "deque" fifo-like object
|
||||||
|
'''
|
||||||
|
|
||||||
|
def __init__(self, limit=10):
|
||||||
|
'''
|
||||||
|
initializes apdu_stack with the maximum of IO to keep track of
|
||||||
|
'''
|
||||||
|
self.apdu_stack = deque([], limit)
|
||||||
|
|
||||||
|
def push(self, apdu_response):
|
||||||
|
'''
|
||||||
|
stacks the returned response into the apdu_stack
|
||||||
|
'''
|
||||||
|
self.apdu_stack.append( apdu_response )
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
'''
|
||||||
|
represents the whole stack of responses pushed on
|
||||||
|
'''
|
||||||
|
s = ''
|
||||||
|
for apdu in self.apdu_stack:
|
||||||
|
s += apdu.__repr__() + '\n'
|
||||||
|
return s
|
||||||
|
|
||||||
|
def __call__(self):
|
||||||
|
'''
|
||||||
|
calling the apdu_stack returns the last response pushed on it
|
||||||
|
'''
|
||||||
|
try:
|
||||||
|
return self.apdu_stack[-1]
|
||||||
|
except IndexError:
|
||||||
|
return None
|
||||||
|
|
Loading…
Reference in New Issue