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:
Harald Welte 2017-08-16 21:08:17 +02:00
parent 5666cd6818
commit 4086758393
6 changed files with 3515 additions and 0 deletions

455
card/FS.py Normal file
View File

@ -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',
}

1704
card/ICC.py Normal file

File diff suppressed because it is too large Load Diff

456
card/SIM.py Normal file
View File

@ -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

539
card/USIM.py Normal file
View File

@ -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()
#

27
card/__init__.py Normal file
View File

@ -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'

334
card/utils.py Normal file
View File

@ -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