aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorHarald Welte <laforge@gnumonks.org>2011-12-07 01:51:46 +0100
committerHarald Welte <laforge@gnumonks.org>2011-12-07 01:51:46 +0100
commitc1cd027c8a65fc255f0d2271e06c525e68ada164 (patch)
tree83d01e854b39cf181f50fc3f671a2aa2c702ba26
Initial check-in of a small command line based tool for (U)SIM auth
-rw-r--r--README165
-rw-r--r--card/FS.py303
-rw-r--r--card/ICC.py1483
-rw-r--r--card/SIM.py274
-rw-r--r--card/USIM.py402
-rw-r--r--card/__init__.py27
-rw-r--r--card/utils.py252
-rwxr-xr-xosmo-sim-auth.py106
8 files changed, 3012 insertions, 0 deletions
diff --git a/README b/README
new file mode 100644
index 0000000..fe46693
--- /dev/null
+++ b/README
@@ -0,0 +1,165 @@
+= osmo-sim-auth =
+
+This is a small script that can be used with a PC-based smart card
+reader to obtain GSM/UMTS authentication parameters from a SIM/USIM
+card.
+
+== prerequisites ==
+
+We assume that you have
+
+* A smart card reader compatible with pcsc-lite
+* Installed python program and pyscard library
+
+
+=== smart card reader ===
+
+Any reader supported by pcsc-lite will work. However, a reader
+compatible with the USB CCID device class is much recommended.
+
+Please verify that the hardware and driver setup is working, e.g. by
+using the 'pcsc_scan' tool included with pcsc-lite. You should get an
+output like:
+{{{
+V 1.4.17 (c) 2001-2009, Ludovic Rousseau <ludovic.rousseau@free.fr>
+Compiled with PC/SC lite version: 1.5.5
+Scanning present readers...
+0: OmniKey CardMan 5121 00 00
+
+Wed Dec 7 01:32:37 2011
+ Reader 0: OmniKey CardMan 5121 00 00
+ Card state: Card inserted, Shared Mode,
+ ATR: 3B 9F 95 80 1F C7 80 31 E0 73 FE 21 13 57 12 29 11 02 01 00 00 C2
+
+ATR: 3B 9F 95 80 1F C7 80 31 E0 73 FE 21 13 57 12 29 11 02 01 00 00 C2
+}}}
+
+plus many more lines of output decoding the ATR.
+
+If you only get
+{{{
+PC/SC device scanner
+V 1.4.17 (c) 2001-2009, Ludovic Rousseau <ludovic.rousseau@free.fr>
+Compiled with PC/SC lite version: 1.5.5
+Scanning present readers...
+0: OmniKey CardMan 5121 00 00
+
+Wed Dec 7 01:35:08 2011
+ Reader 0: OmniKey CardMan 5121 00 00
+ Card state: Card removed,
+}}}
+
+then your card was not detected in the reader.
+
+If you don't even get any displayed readers, your hardware and/or driver
+setup are likely wrong.
+
+
+=== pyscard ===
+
+pyscard can be installed from packages of major Linux distributions.
+
+If you want to build it from source, it is available from
+http://pyscard.sourceforge.net/
+
+
+== running osmo-sim-auth ==
+
+{{{
+$ ./osmo-sim-auth.py --help
+Usage: osmo-sim-auth.py [options]
+
+Options:
+ -h, --help show this help message and exit
+ -a AUTN, --autn=AUTN AUTN parameter from AuC
+ -r RAND, --rand=RAND RAND parameter from AuC
+ -d, --debug Enable debug output
+ -s, --sim SIM mode (default: USIM)
+}}}
+
+you can run the program in two modes:
+ * running GSM authentication (classic SIM card protocol)
+ * running UMTS authentication (USIM card protocol)
+
+=== classic GSM authentication ===
+
+This mode will use the "RUN GSM ALGORITHM" command as specified in GMS
+TS 11.11
+
+You have to specify
+ * the 16 byte RAND value from the AuC (-r) as 32 hex digits
+ * the '-s' flag to enable SIM mode
+
+{{{
+$ ./osmo-sim-auth.py -r 00000000000000000000000000000000 -s
+Testing SIM card with IMSI 901700000000403
+
+GSM Authentication
+SRES: 215fdb4d
+Kc: 6de816a759a42912
+}}}
+
+=== UMTS authentication ===
+
+This mode will use the "AUTHENTICATE" command as specified in 3GPP TS
+31.102
+
+You have to specify
+ * the 16 byte RAND value from the AuC (-r) as 32 hex digits
+ * the 16 byte AUTN value from the AuC (-a) as 32 hex digits
+
+==== successful operation ====
+
+In this case, the tool will output the following values obtained from
+the card:
+ * RES authentication result value
+ * CK ciphering key
+ * IK integrity key
+ * Kc for inter-RAN handover from UMTS -> 2G
+
+Secondly, the tool will re-run the authentication in "2G authentication
+context" in order to obtain the SRES result. This value would be used
+if a 3G/2G dual-mode phone registers on a 2G network.
+
+{{{
+python ./osmo-sim-auth.py -r 00000000000000000000000000000000 -a ec9320c2c2000000e1dd22c1ad3e2d3d
+[+] UICC AID found:
+found [AID 1] 3GPP || USIM || (255, 134) || (255, 255) || (137, 255,
+255, 255, 255)
+[+] USIM AID selection succeeded
+
+Testing USIM card with IMSI 901700000000403
+
+UMTS Authentication
+RES: e9fc88ccc8a35381
+CK: 7200a184d8f2c758fbdf87900ddbf275
+IK: 12cb2dd3e0ec8378f6fc1d606c619f47
+Kc: 6de816a759a42912
+
+GSM Authentication
+SRES: 215fdb4d
+Kc: 6de816a759a42912
+}}}
+
+==== synchronization required ====
+
+In this case, the AUTHENTICATE command will return the AUTS parameter,
+which has to be sent to the AuC in order to re-synchronzie the SQN
+counter which is kept in both the USIM as well as the AuC.
+
+{{{
+./osmo-sim-auth.py -r 00000000000000000000000000000000 -a ec9320c2c2120000c8b7de2a3449f1bd
+[+] UICC AID found:
+found [AID 1] 3GPP || USIM || (255, 134) || (255, 255) || (137, 255,
+255, 255, 255)
+[+] USIM AID selection succeeded
+
+Testing USIM card with IMSI 901700000000403
+
+UMTS Authentication
+AUTS: 8711a0ec9e2be2f766881a64605b
+
+GSM Authentication
+SRES: 215fdb4d
+Kc: 6de816a759a42912
+}}}
diff --git a/card/FS.py b/card/FS.py
new file mode 100644
index 0000000..2d51e2b
--- /dev/null
+++ b/card/FS.py
@@ -0,0 +1,303 @@
+"""
+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_ELP',
+(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_GLOBST',
+(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_HPPLMN',
+(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, 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',
+}
+
+
+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_HPPLMN',
+(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',
+}
+
+EMV_AID = {
+(160, 0, 0, 0, 3, 16, 16) : 'VISA credit / debit',
+} \ No newline at end of file
diff --git a/card/ICC.py b/card/ICC.py
new file mode 100644
index 0000000..a9cfc88
--- /dev/null
+++ b/card/ICC.py
@@ -0,0 +1,1483 @@
+"""
+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
+# smartcard defined with ISO 7816
+#
+# Specially designed SIM and USIM class
+# for ETSI / 3GPP cards
+#
+# needs pyscard from:
+# http://pyscard.sourceforge.net/
+#################################
+
+# classic python modules
+import os
+import re
+
+# smartcard python modules from pyscard
+from smartcard.CardType import AnyCardType
+from smartcard.CardRequest import CardRequest
+from smartcard.CardConnection import CardConnection
+from smartcard.ATR import ATR
+from smartcard.Exceptions import CardConnectionException
+from smartcard.util import toHexString
+
+from card.utils import *
+
+###########################################################
+# ISO7816 class with attributes and methods as defined
+# by ISO-7816 part 4 standard for smartcard
+###########################################################
+
+class ISO7816(object):
+ '''
+ define attributes, methods and facilities for ISO-7816-4 standard smartcard
+
+ use self.dbg = 1 or more to print live debugging information
+ standard instructions codes available in "INS_dic" class dictionnary
+ standard file tags available in "file_tags" class dictionnary
+ '''
+
+ dbg = 0
+
+ INS_dic = {
+ 0x04 : 'DEACTIVATE FILE',
+ 0x0C : 'ERASE RECORD(S)',
+ 0x0E : 'ERASE BINARY',
+ 0x0F : 'ERASE BINARY',
+ 0x10 : 'TERMINAL PROFILE',
+ 0x12 : 'FETCH',
+ 0x14 : 'TERMINAL RESPONSE',
+ 0x20 : 'VERIFY',
+ 0x21 : 'VERIFY',
+ 0x22 : 'MANAGE SECURITY ENVIRONMENT',
+ 0x24 : 'CHANGE PIN',
+ 0x26 : 'DISABLE PIN',
+ 0x28 : 'ENABLE PIN',
+ 0x2A : 'PERFORM SECURITY OPERATION',
+ 0x2C : 'UNBLOCK PIN',
+ 0x32 : 'INCREASE',
+ 0x44 : 'ACTIVATE FILE',
+ 0x46 : 'GENERATE ASYMETRIC KEY PAIR',
+ 0x70 : 'MANAGE CHANNEL',
+ 0x73 : 'MANAGE SECURE CHANNEL',
+ 0x75 : 'TRANSACT DATA',
+ 0x84 : 'GET CHALLENGE',
+ 0x86 : 'GENERAL AUTHENTICATE',
+ 0x87 : 'GENERAL AUTHENTICATE',
+ 0x88 : 'INTERNAL AUTHENTICATE',
+ 0x89 : 'AUTHENTICATE',
+ 0xA0 : 'SEARCH BINARY',
+ 0xA1 : 'SEARCH BINARY',
+ 0xA2 : 'SEARCH RECORD',
+ 0xA4 : 'SELECT FILE',
+ 0xAA : 'TERMINAL CAPABILITY',
+ 0xB0 : 'READ BINARY',
+ 0xB1 : 'READ BINARY',
+ 0xB2 : 'READ RECORD(S)',
+ 0xB3 : 'READ RECORD(S)',
+ 0xC0 : 'GET RESPONSE',
+ 0xC2 : 'ENVELOPE',
+ 0xC3 : 'ENVELOPE',
+ 0xCA : 'RETRIEVE DATA',
+ 0xCB : 'RETRIEVE DATA',
+ 0xD2 : 'WRITE RECORD',
+ 0xD6 : 'UPDATE BINARY',
+ 0xD7 : 'UPDATE BINARY',
+ 0xDA : 'SET DATA',
+ 0xDB : 'SET DATA',
+ 0xDC : 'UPDATE RECORD',
+ 0xDD : 'UPDATE RECORD',
+ 0xE0 : 'CREATE FILE',
+ 0xE2 : 'APPEND RECORD',
+ 0xE4 : 'DELETE FILE',
+ 0xE6 : 'TERMINATE DF',
+ 0xE8 : 'TERMINATE EF',
+ 0xF2 : 'STATUS',
+ 0xFE : 'TERMINATE CARD USAGE',
+ }
+
+ file_tags = {
+ 0x80 : 'Size',
+ 0x81 : 'Length',
+ 0x82 : 'File Descriptor',
+ 0x83 : 'File Identifier',
+ 0x84 : 'DF Name',
+ 0x85 : 'Proprietary no-BERTLV',
+ 0x86 : 'Proprietary Security Attribute',
+ 0x87 : 'EF with FCI extension',
+ 0x88 : 'Short File Identifier',
+ 0x8A : 'Life Cycle Status',
+ 0x8B : 'Security Attributes ref to expanded',
+ 0x8C : 'Security Attributes compact',
+ 0x8D : 'EF with Security Environment',
+ 0x8E : 'Channel Security Attribute',
+ 0xA0 : 'Security Attribute for DO',
+ 0xA1 : 'Proprietary Security Attribute',
+ 0xA2 : 'DO Pairs',
+ 0xA5 : 'Proprietary BERTLV',
+ 0xAB : 'Security Attribute expanded',
+ }
+
+ def __init__(self, CLA=0x00):
+ '''
+ connect smartcard and defines class CLA code for communication
+ uses "pyscard" library services
+
+ creates self.CLA attribute with CLA code
+ and self.coms attribute with associated "apdu_stack" instance
+ '''
+ cardtype = AnyCardType()
+ cardrequest = CardRequest(timeout=1, cardType=cardtype)
+ self.cardservice = cardrequest.waitforcard()
+ self.cardservice.connection.connect()
+ self.reader = self.cardservice.connection.getReader()
+ self.ATR = self.cardservice.connection.getATR()
+
+ self.CLA = CLA
+ self.coms = apdu_stack()
+
+ def disconnect(self):
+ '''
+ disconnect smartcard: stops the session
+ uses "pyscard" library service
+ '''
+ self.cardservice.connection.disconnect()
+
+ def define_class(self, CLA=0x00):
+ '''
+ define smartcard class attribute for APDU command
+ override CLA value defined in class initialization
+ '''
+ self.CLA = CLA
+
+ def ATR_scan(self, smlist_file="/usr/local/share/pcsc/smartcard_list.txt"):
+ '''
+ print smartcard info retrieved from AnswerToReset
+ thanks to pyscard routine
+
+ if pcsc_scan is installed,
+ use the signature file passed as argument for guessing the card
+
+ check also the more complete "parseATR" tool
+ '''
+ print '\nsmartcard reader: ', self.reader
+ if self.ATR != None:
+ print "\nsmart card ATR is: %s" % toHexString(self.ATR)
+ print 'ATR analysis: '
+ print ATR(self.ATR).dump()
+ print '\nhistorical bytes: ', \
+ toHexString(ATR(self.ATR).getHistoricalBytes())
+ ATRcs = ATR(self.ATR).getChecksum()
+ if ATRcs :
+ print 'checksum: ', "0x%X" % ATRcs
+ else:
+ print 'no ATR checksum'
+ print "\nusing pcsc_scan ATR list file: %s" % smlist_file
+ if os.path.exists(smlist_file):
+ smlist = open(smlist_file).readlines()
+ ATRre = re.compile('(^3[BF]){1}.{1,}$')
+ ATRfinger = ''
+ j = 1
+ for i in range(len(smlist)):
+ if ATRre.match(smlist[i]):
+ if re.compile(smlist[i][:len(smlist[i])-1]).\
+ match(toHexString(self.ATR)):
+ while re.compile('\t.{1,}').match(smlist[i+j]):
+ ATRfinger += smlist[i+j][1:]
+ j += j
+ if ATRfinger == '' :
+ print "no ATR fingerprint found in file: %s" % smlist_file
+ else:
+ print "smartcard ATR fingerprint:\n%s" % ATRfinger
+ else:
+ print "%s file not found" % smlist_file
+
+ def sw_status(self, sw1, sw2):
+ '''
+ sw_status(sw1=int, sw2=int) -> string
+
+ SW status bytes interpretation as defined in ISO-7816 part 4 standard
+ helps to speak and understand with the smartcard!
+ '''
+ status = 'undefined status'
+ if sw1 == 0x90 and sw2 == 0x00: status = 'normal processing: ' \
+ 'command accepted: no further qualification'
+ elif sw1 == 0x61: status = 'normal processing: %i bytes ' \
+ 'still available' % sw2
+ elif sw1 == 0x62:
+ status = 'warning processing: state of non-volatile '\
+ 'memory unchanged'
+ if sw2 == 0x00: status += ': no information given'
+ elif sw2 == 0x81: status += ': part of returned data may' \
+ 'be corrupted'
+ elif sw2 == 0x82: status += ': end of file/record reached ' \
+ 'before reading Le bytes'
+ elif sw2 == 0x83: status += ': selected file invalidated'
+ elif sw2 == 0x84: status += ': FCI not formatted'
+ elif sw2 == 0x85: status += ': selected file in termination state'
+ elif sw2 == 0x86: status += ': no input data available ' \
+ 'from a sensor on the card'
+ elif 0x01 < sw2 < 0x81: status += ': card has %s bytes pending' \
+ % toHexString([sw2])[1]
+ else: status += ': undefined SW2 code: 0x%s' % toHexString([sw2])
+ elif sw1 == 0x63:
+ status = 'warning processing: state of non-volatile memory changed'
+ if sw2 == 0x00: status += ': no information given'
+ elif sw2 == 0x81: status += ': file filled up by the last write'
+ elif 0xC0 <= sw2 <= 0xCF: status += ': counter provided by %s' \
+ % toHexString([sw2])[1]
+ else: status += ': undefined SW2 code: 0x%s' % toHexString([sw2])
+ elif sw1 == 0x64:
+ status = 'execution error: state of non-volatile memory unchanged'
+ if sw2 == 0x01: status += ': immediate response expected ' \
+ 'by the card'
+ elif 0x01 < sw2 < 0x81: status += ': command aborted ' \
+ 'by the card, recovery of %s bytes is needed' \
+ % toHexString([sw2])
+ else: status += ': undefined SW2 code: 0x%s' % toHexString([sw2])
+ elif sw1 == 0x65:
+ status = 'execution error: state of non-volatile memory changed'
+ if sw2 == 0x00: status += ': no information given'
+ elif sw2 == 0x81: status += ': memory failure'
+ else: status += ': undefined SW2 code: 0x%s' % toHexString([sw2])
+ elif sw1 == 0x66: status = 'execution error: reserved for ' \
+ 'security-related issues'
+ elif sw1 == 0x67 and sw2 == 0x00: status = 'checking error: ' \
+ 'wrong length (P3 parameter)'
+ elif sw1 == 0x68:
+ status = 'checking error: functions in CLA not supported'
+ if sw2 == 0x00: status += ': no information given'
+ elif sw2 == 0x81: status += ': logical channel not supported'
+ elif sw2 == 0x82: status += ': secure messaging not supported'
+ elif sw2 == 0x83: status += ': last command of the chain expected'
+ elif sw2 == 0x84: status += ': command chaining not supported'
+ else: status += ': undefined SW2 code: 0x%s' % toHexString([sw2])
+ elif sw1 == 0x69:
+ status = 'checking error: command not allowed'
+ if sw2 == 0x00: status += ': no information given'
+ elif sw2 == 0x81: status += ': command incompatible with ' \
+ 'file structure'
+ elif sw2 == 0x82: status += ': security status not satisfied'
+ elif sw2 == 0x83: status += ': authentication method blocked'
+ elif sw2 == 0x84: status += ': referenced data invalidated'
+ elif sw2 == 0x85: status += ': conditions of use not satisfied'
+ elif sw2 == 0x86: status += ': command not allowed (no current EF)'
+ elif sw2 == 0x87: status += ': expected SM data objects missing'
+ elif sw2 == 0x88: status += ': SM data objects incorrect'
+ else: status += ': undefined SW2 code: 0x%s' % toHexString([sw2])
+ elif sw1 == 0x6A:
+ status = 'checking error: wrong parameter(s) P1-P2'
+ if sw2 == 0x00: status += ': no information given'
+ elif sw2 == 0x80: status += ': incorrect parameters ' \
+ 'in the data field'
+ elif sw2 == 0x81: status += ': function not supported'
+ elif sw2 == 0x82: status += ': file not found'
+ elif sw2 == 0x83: status += ': record not found'
+ elif sw2 == 0x84: status += ': not enough memory space in the file'
+ elif sw2 == 0x85: status += ': Lc inconsistent with TLV structure'
+ elif sw2 == 0x86: status += ': incorrect parameters P1-P2'
+ elif sw2 == 0x87: status += ': Lc inconsistent with P1-P2'
+ elif sw2 == 0x88: status += ': referenced data not found'
+ elif sw2 == 0x89: status += ': file already exists'
+ elif sw2 == 0x8A: status += ': DF name already exists'
+ else: status += ': undefined SW2 code: 0x%s' % toHexString([sw2])
+ elif sw1 == 0x6B and sw2 == 0x00: status = 'checking error: '\
+ 'wrong parameter(s) P1-P2'
+ elif sw1 == 0x6C: status = 'checking error: wrong length Le: ' \
+ 'exact length is %s' % toHexString([sw2])
+ elif sw1 == 0x6D and sw2 == 0x00: status = 'checking error: ' \
+ 'instruction code not supported or invalid'
+ elif sw1 == 0x6E and sw2 == 0x00: status = 'checking error: ' \
+ 'class not supported'
+ elif sw1 == 0x6F and sw2 == 0x00: status = 'checking error: ' \
+ 'no precise diagnosis'
+ return status
+
+ def sr_apdu(self, apdu, force=False):
+ '''
+ sr_apdu(apdu=[0x.., 0x.., ...]) ->
+ list [ string(apdu sent information),
+ string(SW codes interpretation),
+ 2-tuple(sw1, sw2),
+ list(response bytes) ]
+
+ generic function to send apdu, receive and interpret response
+ force: force card reconnection if pyscard transmission fails
+ '''
+ if force:
+ try:
+ data, sw1, sw2 = self.cardservice.connection.transmit(apdu)
+ except CardConnectionException:
+ ISO7816.__init__(self, CLA = self.CLA)
+ data, sw1, sw2 = self.cardservice.connection.transmit(apdu)
+ else:
+ data, sw1, sw2 = self.cardservice.connection.transmit(apdu)
+ # replaces INS code by strings when available
+ if apdu[1] in self.INS_dic.keys():
+ apdu_name = self.INS_dic[apdu[1]] + ' '
+ else:
+ apdu_name = ''
+ sw_stat = self.sw_status(sw1, sw2)
+ return ['%sapdu: %s' % (apdu_name, toHexString(apdu)),
+ 'sw1, sw2: %s - %s' % ( toHexString([sw1, sw2]), sw_stat ),
+ (sw1, sw2),
+ data ]
+
+ def bf_cla(self, start=0, param=[0xA4, 0x00, 0x00, 0x02, 0x3F, 0x00]):
+ '''
+ bf_cla( start=int(starting CLA),
+ param=list(bytes for selecting file 0x3F, 0x00) ) ->
+ list( CLA which could be supported )
+
+ tries all classes CLA codes to check the possibly supported ones
+ prints CLA suspected to be supported
+ returns the list of those CLA codes
+
+ WARNING:
+ can block the card definitively
+ Do not do it with your own VISA / MASTERCARD
+ '''
+ clist = []
+ for i in range(start, 256):
+ ret = self.sr_apdu([i] + param)
+ if ret[2] != (0x6E, 0x00):
+ print ret
+ clist.append(i)
+ return clist
+
+ def bf_ins(self, start=0):
+ '''
+ bf_cla( start=int(starting INS) )
+ -> list( INS which could be supported )
+
+ tries all instructions INS codes to check the supported ones
+ prints INS suspected to be supported
+ returns the list of those INS codes
+
+ WARNING:
+ can block the card definitively
+ Do not do it with your own VISA / MASTERCARD
+ '''
+ ilist = []
+ for i in range(start, 256):
+ if self.dbg > 1:
+ print 'DEBUG: testing %d for INS code with %d CLA code' \
+ % (i, self.CLA)
+ ret = self.sr_apdu([self.CLA, i, 0x00, 0x00])
+ if ret[2] != (0x6D, 0x00):
+ print ret
+ ilist.append(i)
+ return ilist
+
+ ###
+ # Below is defined a list of standard commands to be used with (U)SIM cards
+ # They are mainly defined and described in
+ # ISO 7816 and described further in ETSI 101.221
+ ###
+ def READ_BINARY(self, P1=0x00, P2=0x00, Le=0x01):
+ '''
+ APDU command to read the content of EF file with transparent structure
+ Le: length of data bytes to be read
+
+ call sr_apdu method
+ '''
+ READ_BINARY = [self.CLA, 0xB0, P1, P2, Le]
+ return self.sr_apdu(READ_BINARY)
+
+ def WRITE_BINARY(self, P1=0x00, P2=0x00, Data=[]):
+ '''
+ APDU command to write the content of EF file with transparent structure
+
+ Data: list of data bytes to be written
+ call sr_apdu method
+ '''
+ WRITE_BINARY = [self.CLA, 0xD0, P1, P2, len(Data)] + Data
+ return self.sr_apdu(WRITE_BINARY)
+
+ def UPDATE_BINARY(self, P1=0x00, P2=0x00, Data=[]):
+ '''
+ APDU command to update the content of EF file with transparent structure
+
+ Data: list of data bytes to be written
+ call sr_apdu method
+ '''
+ UPDATE_BINARY = [self.CLA, 0xD6, P1, P2, len(Data)] + Data
+ return self.sr_apdu(UPDATE_BINARY)
+
+ def ERASE_BINARY(self, P1=0x00, P2=0x00, Lc=None, Data=[]):
+ '''
+ APDU command to erase the content of EF file with transparent structure
+
+ Lc: 'None' or '0x02'
+ Data: list of data bytes to be written
+ call sr_apdu method
+ '''
+ if Lc is None:
+ ERASE_BINARY = [self.CLA, 0x0E, P1, P2]
+ else:
+ ERASE_BINARY = [self.CLA, 0x0E, P1, P2, 0x02] + Data
+ return self.sr_apdu(ERASE_BINARY)
+
+ def READ_RECORD(self, P1=0x00, P2=0x00, Le=0x00):
+ '''
+ APDU command to read the content of EF file with record structure
+
+ P1: record number
+ P2: reference control
+ Le: length of data bytes to be read
+ call sr_apdu method
+ '''
+ READ_RECORD = [self.CLA, 0xB2, P1, P2, Le]
+ return self.sr_apdu(READ_RECORD)
+
+ def WRITE_RECORD(self, P1=0x00, P2=0x00, Data=[]):
+ '''
+ APDU command to write the content of EF file with record structure
+
+ P1: record number
+ P2: reference control
+ Data: list of data bytes to be written in the record
+ call sr_apdu method
+ '''
+ WRITE_RECORD = [self.CLA, 0xD2, P1, P2, len(Data)] + Data
+ return self.sr_apdu(WRITE_RECORD)
+
+ def APPEND_RECORD(self, P2=0x00, Data=[]):
+ '''
+ APDU command to append a record on EF file with record structure
+
+ P2: reference control
+ Data: list of data bytes to be appended on the record
+ call sr_apdu method
+ '''
+ APPEND_RECORD = [self.CLA, 0xE2, 0x00, P2, len(Data)] + Data
+ return self.sr_apdu(APPEND_RECORD)
+
+ def UPDATE_RECORD(self, P1=0x00, P2=0x00, Data=[]):
+ '''
+ APDU command to update the content of EF file with record structure
+
+ P1: record number
+ P2: reference control
+ Data: list of data bytes to update the record
+ call sr_apdu method
+ '''
+ APPEND_RECORD = [self.CLA, 0xDC, P1, P2, len(Data)] + Data
+ return self.sr_apdu(APPEND_RECORD)
+
+ def GET_DATA(self, P1=0x00, P2=0x00, Le=0x01):
+ '''
+ APDU command to retrieve data object
+
+ P1 and P2: reference control for data object description
+ Le: number of bytes expected in the response
+ call sr_apdu method
+ '''
+ GET_DATA = [self.CLA, 0xCA, P1, P2, Le]
+ return self.sr_apdu(GET_DATA)
+
+ def PUT_DATA(self, P1=0x00, P2=0x00, Data=[]):
+ '''
+ APDU command to store data object
+
+ P1 and P2: reference control for data object description
+ Data: list of data bytes to put in the data object structure
+ call sr_apdu method
+ '''
+ if len(Data) == 0:
+ PUT_DATA = [self.CLA, 0xDA, P1, P2]
+ elif 1 <= len(Data) <= 255:
+ PUT_DATA = [self.CLA, 0xDA, P1, P2, len(Data)] + Data
+ # should never be the case, however... who wants to try
+ else:
+ PUT_DATA = [self.CLA, 0xDA, P1, P2, 0xFF] + Data[0:255]
+ return self.sr_apdu(PUT_DATA)
+
+ def SELECT_FILE(self, P1=0x00, P2=0x00, Data=[0x3F, 0x00], \
+ with_length=True):
+ '''
+ APDU command to select file
+
+ P1 and P2: selection control
+ Data: list of bytes describing the file identifier or address
+ call sr_apdu method
+ '''
+ if with_length:
+ Data = [min(len(Data), 255)] + Data
+ SELECT_FILE = [self.CLA, 0xA4, P1, P2] + Data
+ return self.sr_apdu(SELECT_FILE)
+
+ def VERIFY(self, P2=0x00, Data=[]):
+ '''
+ APDU command to verify user PIN, password or security codes
+
+ P2: reference control
+ Data: list of bytes to be verified by the card
+ call sr_apdu method
+ '''
+ if len(Data) == 0:
+ VERIFY = [self.CLA, 0x20, 0x00, P2]
+ elif 1 <= len(Data) <= 255:
+ VERIFY = [self.CLA, 0x20, 0x00, P2, len(Data)] + Data
+ # should never be the case, however... who wants to try
+ else:
+ VERIFY = [self.CLA, 0x20, 0x00, P2, 0xFF] + Data[0:255]
+ return self.sr_apdu(VERIFY)
+
+ def INTERNAL_AUTHENTICATE(self, P1=0x00, P2=0x00, Data=[]):
+ '''
+ APDU command to run internal authentication algorithm
+
+ P1 and P2: reference control (algo, secret key selection...)
+ Data: list of bytes containing the authentication challenge
+ call sr_apdu method
+ '''
+ INTERNAL_AUTHENTICATE = [self.CLA, 0x88, P1, P2, len(Data)] + Data
+ return self.sr_apdu(INTERNAL_AUTHENTICATE)
+
+ def EXTERNAL_AUTHENTICATE(self, P1=0x00, P2=0x00, Data=[]):
+ '''
+ APDU command to conditionally update the security status of the card
+ after getting a challenge from it
+
+ P1 and P2: reference control (algo, secret key selection...)
+ Data: list of bytes containing the challenge response
+ call sr_apdu method
+ '''
+ if len(Data) == 0:
+ EXTERNAL_AUTHENTICATE = [self.CLA, 0x82, P1, P2]
+ elif 1 <= len(Data) <= 255:
+ EXTERNAL_AUTHENTICATE = [self.CLA, 0x82, P1, P2, len(Data)] + Data
+ # should never be the case, however... who wants to try
+ else:
+ EXTERNAL_AUTHENTICATE = [self.CLA, 0x82, P1, P2, 0xFF] + Data[0:255]
+ return self.sr_apdu(EXTERNAL_AUTHENTICATE)
+
+ def GET_CHALLENGE(self):
+ '''
+ APDU command to get a challenge for external entity authentication
+ to the card
+
+ call sr_apdu method
+ '''
+ GET_CHALLENGE = [self.CLA, 0x84, 0x00, 0x00]
+ return self.sr_apdu(GET_CHALLENGE)
+
+ def MANAGE_CHANNEL(self, P1=0x00, P2=0x00):
+ '''
+ APDU to open and close supplementary logical channels
+
+ P1=0x00 to open, 0x80 to close
+ P2=0x00, 1, 2 or 3 to ask for logical channel number
+ call sr_apdu method
+ '''
+ if (P1, P2) == (0x00, 0x00):
+ MANAGE_CHANNEL = [self.CLA, 0x70, P1, P2, 0x01]
+ else:
+ MANAGE_CHANNEL = [self.CLA, 0x70, P1, P2]
+ return self.sr_apdu(MANAGE_CHANNEL)
+
+ def GET_RESPONSE(self, Le=0x01):
+ '''
+ APDU command to retrieve data after selection
+ or other kind of request that should get an extensive reply
+
+ Le: expected length of data
+ call sr_apdu method
+ '''
+ GET_RESPONSE = [self.CLA, 0xC0, 0x00, 0x00, Le]
+ return self.sr_apdu(GET_RESPONSE)
+
+ def ENVELOPPE(self, Data=[]):
+ '''
+ APDU command to encapsulate data (APDU or other...)
+ check ETSI TS 102.221 for some examples...
+
+ Data: list of bytes
+ call sr_apdu method
+ '''
+ if len(Data) == 0:
+ ENVELOPPE = [self.CLA, 0xC2, 0x00, 0x00]
+ elif 1 <= len(Data) <= 255:
+ ENVELOPPE = [self.CLA, 0xC2, 0x00, 0x00, len(Data)] + Data
+ return self.sr_apdu(ENVELOPPE)
+
+ def SEARCH_RECORD(self, P1=0x00, P2=0x00, Data=[]):
+ '''
+ APDU command to seach pattern in the current EF file
+ with record structure
+
+ P1: record number
+ P2: type of search
+ Data: list of bytes describing a pattern to search for
+ call sr_apdu method
+ '''
+ SEARCH_RECORD = [self.CLA, 0xA2, P1, P2, len(Data)] + Data
+ return self.sr_apdu(SEARCH_RECORD)
+
+ def DISABLE_CHV(self, P1=0x00, P2=0x00, Data=[]):
+ '''
+ APDU command to disable CHV verification (such as PIN or password...)
+
+ P1: let to 0x00... or read ISO and ETSI specifications
+ P2: type of CHV to disable
+ Data: list of bytes for CHV value
+ call sr_apdu method
+ '''
+ DISABLE_CHV = [self.CLA, 0x26, P1, P2, len(Data)] + Data
+ return self.sr_apdu(DISABLE_CHV)
+
+ def UNBLOCK_CHV(self, P2=0x00, Lc=None, Data=[]):
+ '''
+ APDU command to unblock CHV code (e.g. with PUK for deblocking PIN)
+
+ P2: type of CHV to unblock
+ Lc: Empty or 0x10
+ Data: if Lc=0x10, UNBLOCK_CHV value and new CHV value to set
+ call sr_apdu method
+
+ TODO: check the exact coding for the Data
+ '''
+ if Lc is None:
+ UNBLOCK_CHV = [self.CLA, 0x2C, 0x00, P2]
+ else:
+ UNBLOCK_CHV = [self.CLA, 0x2C, 0x00, P2, 0x10] + Data
+ return self.sr_apdu(UNBLOCK_CHV)
+
+ ##########################
+ # evolved "macro" method for ISO7816 card
+ # need the "coms" attribute being an apdu_stack()
+ ##########################
+ def parse_file(self, Data=[]):
+ '''
+ parse_file(self, Data) -> Dict()
+
+ parses a list of bytes returned when selecting a file
+ interprets the content of some informative bytes
+ for file structure and parsing method...
+ '''
+ ber = BERTLV_parser( Data )
+ if self.dbg > 1:
+ print '[DBG] BER structure:\n%s' % ber
+ if len(ber) > 1:
+ # TODO: implements recursive BER object parsing
+ print '[WNG] more than 1 BER object: %s' % ber
+
+ # for FCP control structure, precise parsing is done
+ # this structure seems to be the most used for (U)SIM cards
+ if ber[0][0][2] == 0x2:
+ fil = self.parse_FCP( ber[0][2] )
+ fil['Control'] = 'FCP'
+ return fil
+
+ # for other control structure, DIY
+ fil = {}
+ if ber[0][0][2] == 0x4:
+ fil['Control'] = 'FMD'
+ if self.dbg:
+ print '[WNG] FMD file structure parsing not implemented'
+ elif ber[0][0][2] == 0xF:
+ fil['Control'] = 'FCI'
+ if self.dbg:
+ print '[WNG] FCI file structure parsing not implemented'
+ else:
+ fil['Control'] = ber[0][0]
+ if self.dbg:
+ print '[WNG] unknown file structure'
+ fil['Data'] = ber[0][2]
+
+ return fil
+
+ def parse_FCP(self, Data=[]):
+ '''
+ parse_FCP(Data) -> Dict()
+
+ parses a list of bytes returned when selecting a file
+ interprets the content of some informative bytes
+ for file structure and parsing method...
+ '''
+ fil = {}
+ # loop on the Data bytes to parse TLV'style attributes
+ toProcess = Data
+ while len(toProcess) > 0:
+ # TODO: seemd full compliancy
+ # would require to work with the BERTLV parser...
+ [T, L, V] = first_TLV_parser(toProcess)
+ if self.dbg > 2:
+ if T in self.file_tags.keys():
+ Tag = self.file_tags[T]
+ else:
+ Tag = T
+ print '[DBG] %s / %s: %s' % (T, Tag, V)
+
+ # do extra processing here
+ # File ID, DF name, Short file id
+ if T in (0x83, 0x84, 0x88):
+ fil[self.file_tags[T]] = V
+ # Security Attributes compact format
+ elif T == 0x8C:
+ fil[self.file_tags[T]] = V
+ fil = self.parse_security_attribute_compact(V, fil)
+ # Security Attributes
+ elif T in (0x86, 0x8B, 0x8E, 0xA0, 0xA1, 0xAB):
+ fil[self.file_tags[T]] = V
+ # TODO: no concrete parsing at this time...
+ fil = self.parse_security_attribute(V, fil)
+ # file size or length
+ elif T in (0x80, 0x81):
+ fil[self.file_tags[T]] = sum( [ V[i] * pow(0x100, len(V)-i-1) \
+ for i in range(len(V)) ] )
+ # file descriptor, deducting file access, type and structure
+ elif T == 0x82:
+ assert( L in (2, 5) )
+ fil[self.file_tags[T]] = V
+ fil = self.parse_file_descriptor(V, fil)
+ # life cycle status
+ elif T == 0x8A:
+ fil = self.parse_life_cycle(V, fil)
+ # proprietary information
+ elif T == 0xA5:
+ fil = self.parse_proprietary(V, fil)
+ else:
+ if T in self.file_tags.keys():
+ fil[self.file_tags[T]] = V
+ else:
+ fil[T] = V
+
+ # truncate the data to process and loop
+ if L < 256:
+ toProcess = toProcess[L+2:]
+ else:
+ toProcess = toProcess[L+4:]
+
+ # and return the file
+ return fil
+
+ @staticmethod
+ def parse_life_cycle(Data, fil):
+ '''
+ parses a list of bytes provided in Data
+ interprets the content as the life cycle
+ and enriches the file dictionnary passed as argument
+ '''
+ if Data[0] == 1: fil['Life Cycle Status'] = 'creation state'
+ elif Data[0] == 3: fil['Life Cycle Status'] = 'initialization state'
+ elif Data[0] in (5, 7): fil['Life Cycle Status'] = 'operational state' \
+ ' - activated'
+ elif Data[0] in (4, 6): fil['Life Cycle Status'] = 'operational state' \
+ ' - deactivated'
+ elif Data[0] in range(12, 15): fil['Life Cycle Status'] = \
+ 'termination state'
+ elif Data[0] >= 16: fil['Life Cycle Status'] = 'proprietary'
+ else: fil['Life Cycle Status'] = 'RFU'
+ return fil
+
+ @staticmethod
+ def parse_file_descriptor(Data, fil):
+ '''
+ parses a list of bytes provided in Data
+ interprets the content as the file descriptor
+ and enriches the file dictionnary passed as argument
+ '''
+ # parse the File Descriptor Byte
+ fd = Data[0]
+ fd_type = (fd >> 3) & 0b00111
+ fd_struct = fd & 0b00000111
+ # get Structure, Access and Type
+ # bit b8
+ if (fd >> 7) & 0b1: fil['Structure'] = 'RFU'
+ # access bit b7
+ if (fd >> 6) & 0b1: fil['Access'] = 'shareable'
+ else : fil['Access'] = 'not shareable'
+ # structure bits b1 to b3
+ if fd_struct == 0: fil['Structure'] = 'no information'
+ elif fd_struct == 1: fil['Structure'] = 'transparent'
+ elif fd_struct == 2: fil['Structure'] = 'linear fixed'
+ elif fd_struct == 3: fil['Structure'] = 'linear fixed TLV'
+ elif fd_struct == 4: fil['Structure'] = 'linear variable'
+ elif fd_struct == 5: fil['Structure'] = 'linear variable TLV'
+ elif fd_struct == 6: fil['Structure'] = 'cyclic'
+ elif fd_struct == 7: fil['Structure'] = 'cyclic TLV'
+ else : fil['Structure'] = 'RFU'
+ # type bits b4 to b6
+ if fd_type == 0: fil['Type'] = 'EF working'
+ elif fd_type == 1: fil['Type'] = 'EF internal'
+ elif fd_type == 7:
+ fil['Type'] = 'DF'
+ if fd_struct == 1: fil['Structure'] = 'BER-TLV'
+ elif fd_struct == 2: fil['Structure'] = 'TLV'
+ else: fil['Type'] = 'EF proprietary'
+
+ # for linear and cyclic EF:
+ # the following is convenient for UICC,
+ # but looks not fully conform to ISO standard
+ # see coding convention in ISO 7816-4 Table 87
+ if len(Data) == 5:
+ fil['Record Length'], fil['Record Number'] = Data[3], Data[4]
+
+ return fil
+
+ @staticmethod
+ def parse_proprietary(Data, fil):
+ '''
+ parses a list of bytes provided in Data
+ interprets the content as the proprietary parameters
+ and enriches the file dictionnary passed as argument
+ '''
+ propr_tags = {
+ 0x80:"UICC characteristics",
+ 0x81:"Application power consumption",
+ 0x82:"Minimum application clock frequency",
+ 0x83:"Amount of available memory",
+ 0x84:"File details",
+ 0x85:"Reserved file size",
+ 0x86:"Maximum file size",
+ 0x87:"Supported system commands",
+ 0x88:"Specific UICC environmental conditions",
+ }
+ while len(Data) > 0:
+ [T, L, V] = first_TLV_parser( Data )
+ if T in propr_tags.keys():
+ fil[propr_tags[T]] = V
+ Data = Data[L+2:]
+ return fil
+
+ @staticmethod
+ def parse_security_attribute_compact(Data, fil):
+ '''
+ parses a list of bytes provided in Data
+ interprets the content as the compact form for security parameters
+ and enriches the file dictionnary passed as argument
+ '''
+ # See ISO-IEC 7816-4 section 5.4.3, with compact and expanded format
+ AM = Data[0]
+ SC = Data[1:]
+ sec = '#'
+
+ if 'Type' in fil.keys():
+ # DF parsing
+ if fil['Type'] == 'DF':
+ if AM & 0b10000000 == 0:
+ if AM & 0b01000000: sec += ' DELETE FILE #'
+ if AM & 0b00100000: sec += ' TERMINATE DF #'
+ if AM & 0b00010000: sec += ' ACTIVATE FILE #'
+ if AM & 0b00001000: sec += ' DEACTIVATE FILE #'
+ if AM & 0b00000100: sec += ' CREATE DF #'
+ if AM & 0b00000010: sec += ' CREATE EF #'
+ if AM & 0b00000001: sec += ' DELETE FILE #'
+ # EF parsing
+ else:
+ if AM & 0b10000000 == 0:
+ if AM & 0b01000000: sec += ' DELETE FILE #'
+ if AM & 0b00100000: sec += ' TERMINATE EF #'
+ if AM & 0b00010000: sec += ' ACTIVATE FILE #'
+ if AM & 0b00001000: sec += ' DEACTIVATE FILE #'
+ if AM & 0b00000100: sec += ' WRITE / APPEND #'
+ if AM & 0b00000010: sec += ' UPDATE / ERASE #'
+ if AM & 0b00000001: sec += ' READ / SEARCH #'
+
+ # loop on SC:
+ for cond in SC:
+ if cond == 0 : sec += ' Always #'
+ elif cond == 0xff: sec += ' Never #'
+ else:
+ sec += ' SEID %s #' % (cond & 0b00001111)
+ if cond & 0b10000000: sec += ' all conditions #'
+ else: sec += ' at least 1 condition #'
+ if cond & 0b01000000: sec += ' secure messaging #'
+ if cond & 0b00100000: sec += ' external authentication #'
+ if cond & 0b00010000: sec += ' user authentication #'
+
+ #file['Security Attributes raw'] = Data
+ fil['Security Attributes'] = sec
+ return fil
+
+ @staticmethod
+ def parse_security_attribute(Data, fil):
+ '''
+ TODO: to implement...
+
+ need to work further on how to do it (with ref to EF_ARR)
+ '''
+ # See ISO-IEC 7816-4 section 5.4.3, with compact and expanded format
+ #if self.dbg:
+ # print '[DBG] parse_security_attribute() not implemented'
+ return fil
+
+ def read_EF(self, fil):
+ '''
+ interprets the content of file parameters (Structure, Size, Length...)
+ and enriches the file dictionnary passed as argument
+ with "Data" key and corresponding
+ - list of bytes for EF transparent
+ - list of list of bytes for cyclic or linear EF
+ '''
+ # read EF transparent data
+ if fil['Structure'] == 'transparent':
+ self.coms.push( self.READ_BINARY(Le=fil['Size']) )
+ if self.coms()[2] != (0x90, 0x00):
+ if self.dbg > 1:
+ print '[DBG] %s' % self.coms()
+ return fil
+ fil['Data'] = self.coms()[3]
+
+ # read EF cyclic / linear all records data
+ elif fil['Structure'] != 'transparent':
+ fil['Data'] = []
+ # for record data: need to check the number of recordings
+ # stored in the file, and iterate for each
+ for i in range( (fil['Size'] / fil['Record Length']) ):
+ self.coms.push( self.READ_RECORD(P1=i+1, P2=0x04, \
+ Le=fil['Record Length']) )
+ if self.coms()[2] != (0x90, 0x00):
+ # should mean there is an issue
+ # somewhere in the file parsing process
+ if self.dbg:
+ print '[WNG] error in iterating the RECORD parsing at' \
+ ' iteration %s\n%s' % (i, self.coms())
+ return fil
+ if self.coms()[3][1:] == len(self.coms()[3][1:]) * [255]:
+ # record is empty, contains padding only
+ pass
+ else:
+ fil['Data'].append(self.coms()[3])
+
+ # return the [Data] for transparent or
+ # [[Record1],[Record2]...] for cyclic / linear
+ return fil
+
+ def select(self, Data=[0x3F, 0x00], typ="fid", with_length=True):
+ '''
+ self.select(Data=[0x.., 0x..], typ="fid", with_length=True)
+ -> dict(file) on success, None on error
+
+ selects the file
+ if error, returns None
+ if processing correct: gets response with info on the file
+ if processing correct and EF file: reads the data in the file
+ works in USIM fashion
+ else returns the data dictionnary: check parse_file_(U)SIM methods
+ last apdu available from the attribute self.coms
+
+ different types of file selection are possible:
+ "fid": select by file id, only the direct child or
+ parent files of the last selected MF / DF / ADF
+ "pmf": select by path from MF
+ "pdf": select by path from last selected MF / DF / ADF
+ (or relative path)
+ "aid": select by ADF (Application) name
+ '''
+ # get the UICC trigger
+ is_UICC = isinstance(self, UICC)
+
+ # handle type of selection:
+ if typ == "pmf": P1 = 0x08
+ elif typ == "pdf": P1 = 0x09
+ elif typ == "aid": P1 = 0x04
+ # the case of selection by "fid":
+ else: P1 = 0x00
+
+ # for UICC instance
+ # ask the return of the FCP template for the selected file:
+ if is_UICC:
+ P2 = 0x04
+ else:
+ P2 = 0x00
+
+ # used to get back to MF without getting MF attributes:
+ if len(Data) == 0:
+ P1, P2 = 0x00, 0x0C
+
+ # select file and check SW; if error, returns None, else get response
+ self.coms.push(self.SELECT_FILE(P1=P1, P2=P2, Data=Data, \
+ with_length=with_length))
+
+ # different SW codes for UICC and old ISO card (e.g. SIM)
+ if is_UICC and self.coms()[2][0] != 0x61 \
+ or not is_UICC and self.coms()[2][0] != 0x9F:
+ if self.dbg > 1:
+ print '[DBG] %s' % self.coms()
+ return None
+
+ # get response and check SW:
+ # if error, return None, else parse file info
+ self.coms.push(self.GET_RESPONSE(Le=self.coms()[2][1]))
+ if self.coms()[2] != (0x90, 0x00):
+ if self.dbg > 1:
+ print '[DBG] %s' % self.coms()
+ return None
+
+ data = self.coms()[3]
+ # take the `parse_file()' method from the instance:
+ # ISO7816, UICC or SIM
+ fil = self.parse_file(data)
+ if fil['Type'][0:2] == 'EF':
+ fil = self.read_EF(fil)
+
+ # finally returns the whole file dictionnary,
+ # containing the ['Data'] key for EF file
+ return fil
+
+ #
+ ###############
+ # TODO:
+ # improve all of the following...
+ ###############
+
+ def flat_files_bf(self, path=[], under_AID=0, \
+ hi_addr=(0, 0xff), lo_addr=(0, 0xff)):
+ '''
+ flat_files_bf(self, path=[], under_AID=0, \
+ hi_addr=(0, 0xff), lo_addr=(0, 0xff))
+ -> list(files), list(DF_to_explore)
+
+ path: path of the DF under MF or AID to brute force
+ under_AID: if > 0, select the AID number to init the brute force
+ only available for UICC instance
+ hi_addr: 8 MSB of the file address to brute force
+ lo_addr: 8 LSB of the file address to brute force
+ with_select_length: use the length parameter with SELECT instruction
+
+ brute force file addresses of direct child under a given DF
+ get information on existing files, and discovered DF
+
+ WARNING: not very tested yet...
+ '''
+ # init return variables
+ FS, DF_to_explore = [], []
+ # init selection process
+ MF, sel_type = [0x3F, 0x00], 'fid'
+ if isinstance(self, UICC):
+ sel_type = 'pdf'
+
+ # start by selecting MF
+ try:
+ r = self.select(MF)
+ except:
+ print '[ERR] selecting MF failed'
+ return
+ if r == None:
+ print '[ERR] MF not found!'
+ return
+
+ #if needed, select AID
+ if isinstance(self, UICC) and under_AID:
+ try:
+ self.get_AID()
+ r = self.select_by_aid(under_AID)
+ except:
+ print '[ERR] selecting AID failed'
+ return
+ if r == None:
+ print '[ERR] AID not found'
+ return
+
+ # place on the DF path to bf
+ # select it by 'path from last selected DF'
+ if len(path) > 0:
+ try:
+ path_init = self.select(path, sel_type)
+ except:
+ print '[ERR] selecting path failed:\n%s' % self.coms()
+ return
+ if path_init == None:
+ print '[ERR] path not found: %s' % path
+ return
+
+ # Dany'style programming
+ def reinit():
+ self.select(MF)
+ if isinstance(self, UICC) and under_AID:
+ self.select_by_aid(under_AID)
+ if len(path) > 0:
+ self.select(path, sel_type)
+
+ # loop over the address space to brute force files
+ i, j = 0, 0
+ for i in range(hi_addr[0], hi_addr[1]):
+ if self.dbg and i%2 == 0:
+ print '[DBG] addr: %s %s %s' % ([hex(v) for v in path], \
+ hex(i), hex(j))
+ for j in range(lo_addr[0], lo_addr[1]):
+ # avoid MF re-selection:
+ if (i, j) == [0x3F, 0x00]:
+ fil = None
+ # select by direct file id
+ else:
+ fil = self.select([i, j], sel_type)
+ if fil is not None:
+ if self.dbg:
+ print '[DBG] found file at path, id: ' \
+ '%s %s' % (path, [i, j])
+ if 'File Identifier' in fil.keys():
+ fil['Absolut Path'] = path+fil['File Identifier']
+ FS.append(fil)
+ if 'Type' in fil.keys() and fil['Type'] == 'DF':
+ reinit()
+ if 'Absolut Path' in fil.keys():
+ DF_to_explore.append(fil['Absolut Path'])
+
+ # re-initialize at MF and return
+ self.select(MF)
+ return FS, DF_to_explore
+
+ def init_FS(self):
+ self.FS = []
+
+ def recu_files_bf(self, path=[], under_AID=0):
+ '''
+ recu_files_bf(self, path=[], under_AID=0)
+ -> void
+
+ fills self.FS attribute with all files and DF discovered
+ recursively
+ '''
+ # list all files and DF on the path
+ ret = self.flat_files_bf(path=path, under_AID=under_AID)
+ try:
+ self.FS += ret[0]
+ except:
+ '[ERR] FS not initialized: %s' % type(self.FS)
+ return
+ DF = ret[1]
+
+ # recursive method call
+ # DF contains absolut path
+ for addr in DF:
+ print '[DBG] path: %s' % addr
+ self.recu_files_bf(path=addr, under_AID=under_AID)
+
+ @staticmethod
+ def __write_dict(dict, fd):
+ keys = dict.keys()
+ keys.sort()
+ fd.write('\n')
+ for k in keys:
+ fd.write('%s: %s\n' % (k, dict[k]))
+
+
+ def scan_fs(self, filename='card_fs', stdout=False):
+ '''
+ bf_files_under_MF(self, output='card_fs', stdout=True)
+ -> void
+
+ filename: file to write found information in
+ stdout: print information on stdout too
+
+ brute force all file addresses from MF and found AID
+ recursively (until no more DF are found)
+ write information on existing file on the output,
+
+ WARNING: not very tested either...
+ '''
+ fd = open(filename, 'w')
+
+ self.init_FS()
+ self.recu_files_bf()
+ fd.write('\n### MF ###\n')
+ for f in self.FS:
+ self.__write_dict(f, fd)
+ fd.write('\n')
+
+ # TODO: loop that
+ #self.init_FS()
+ #self.recu_files_bf(under_AID=1)
+ #fd.write('\n### AID #1 ###\n')
+ #for f in self.FS:
+ # self.__write_dict(f, fd)
+ # fd.write('\n')
+
+ fd.close()
+#
+
+##############################################
+# UICC is defined in ETSI 102.221 mainly, and used for many telco applications
+##############################################
+
+class UICC(ISO7816):
+ '''
+ define attributes, methods and facilities for ETSI UICC card
+ check UICC specifications mainly in ETSI TS 102.221
+
+ inherits (eventually overrides) methods and objects from ISO7816 class
+ use self.dbg = 1 or more to print live debugging information
+ '''
+ AID_RID = {
+ (0xA0, 0x00, 0x00, 0x00, 0x09): 'ETSI',
+ (0xA0, 0x00, 0x00, 0x00, 0x87): '3GPP',
+ (0xA0, 0x00, 0x00, 0x03, 0x43): '3GPP2',
+ (0xA0, 0x00, 0x00, 0x04, 0x12): 'OMA',
+ (0xA0, 0x00, 0x00, 0x04, 0x24): 'WiMAX',
+ }
+ ETSI_AID_app_code = {
+ (0x00, 0x00): 'Reserved',
+ (0x00, 0x01): 'GSM',
+ (0x00, 0x02): 'GSM SIM Toolkit',
+ (0x00, 0x03): 'GSM SIM API for JavaCard',
+ (0x00, 0x04): 'Tetra',
+ (0x00, 0x05): 'UICC API for JavaCard',
+ (0x01, 0x01): 'DVB CBMS KMS',
+ }
+ GPP_AID_app_code = {
+ (0x10, 0x01): 'UICC',
+ (0x10, 0x02): 'USIM',
+ (0x10, 0x03): 'USIM Toolkit',
+ (0x10, 0x04): 'ISIM',
+ (0x10, 0x05): 'USIM API for JavaCard',
+ (0x10, 0x06): 'ISIM API for JavaCard',
+ (0x10, 0x05): 'Contact Manager API for JavaCard',
+ }
+ GPP2_AID_app_code = {
+ (0x10, 0x02): 'CSIM',
+ }
+ AID_country_code = {
+ (0xFF, 0x33): 'France',
+ (0xFF, 0x44): 'United Kingdom',
+ (0xFF, 0x49): 'Germany',
+ }
+
+ pin_status = {
+ 0x01 : "PIN Appl 1",
+ 0x02 : "PIN Appl 2",
+ 0x03 : "PIN Appl 3",
+ 0x04 : "PIN Appl 4",
+ 0x05 : "PIN Appl 5",
+ 0x06 : "PIN Appl 6",
+ 0x07 : "PIN Appl 7",
+ 0x08 : "PIN Appl 8",
+ 0x09 : "RFU",
+ 0x0A : "ADM1",
+ 0x0B : "ADM2",
+ 0x0C : "ADM3",
+ 0x0D : "ADM4",
+ 0x0E : "ADM5",
+ 0x11 : "PIN Universal PIN",
+ 0x81 : "Second PIN Appl 1",
+ 0x82 : "Second PIN Appl 2",
+ 0x83 : "Second PIN Appl 3",
+ 0x84 : "Second PIN Appl 4",
+ 0x85 : "Second PIN Appl 5",
+ 0x86 : "Second PIN Appl 6",
+ 0x87 : "Second PIN Appl 7",
+ 0x88 : "Second PIN Appl 8",
+ 0x89 : "RFU",
+ 0x8A : "ADM6",
+ 0x8B : "ADM7",
+ 0x8C : "ADM8",
+ 0x8D : "ADM9",
+ 0x8E : "ADM10",
+ }
+
+ files = [
+ ([0x3F, 0x00], 'MF', 'MF'),
+ ([0x2F, 0x00], 'EF', 'EF_DIR'),
+ ([0x2F, 0x01], 'EF', 'EF_ATR'),
+ ([0x2F, 0x05], 'EF', 'EF_PL'),
+ ([0x2F, 0x06], 'EF', 'EF_ARR'),
+ ([0x2F, 0x2E], 'EF', 'EF_ICCID'),
+ ([0x7F, 0xFF], 'DF', 'current ADF'),
+ ([0x7F, 0x10], 'DF', 'DF_TELECOM'),
+ ([0x7F, 0x10, 0x5F, 0x50], 'DF', 'DF_GRAPHICS'),
+ ([0x7F, 0x10, 0x5F, 0x3A], 'DF', 'DF_PHONEBOOK'),
+ ([0x7F, 0x20], 'DF', 'DF_GSM'),
+ ([0x7F, 0x21], 'DF', 'DF_DCS1800'),
+ ([0x7F, 0x22], 'DF', 'DF_IS-41'),
+ ([0x7F, 0x23], 'DF', 'DF_FP-CTS'),
+ ([0x7F, 0x24], 'DF', 'DF_TIA-EIA136'),
+ ([0x7F, 0x25], 'DF', 'DF_TIA-EIA95'),
+ ([0x7F, 0x80], 'DF', 'DF_PDC'),
+ ([0x7F, 0x90], 'DF', 'DF_TETRA'),
+ ([0x7F, 0x31], 'DF', 'DF_iDEN'),
+ ]
+
+ def __init__(self):
+ '''
+ initializes like an ISO7816-4 card with CLA=0x00
+ and check available AID (Application ID) read from EF_DIR
+
+ initializes on the MF
+ '''
+ ISO7816.__init__(self, CLA=0x00)
+ self.AID = []
+
+ if self.dbg:
+ print '[DBG] type definition: %s' % type(self)
+ print '[DBG] CLA definition: %s' % hex(self.CLA)
+ #print '[DBG] EF_DIR file selection and reading...'
+
+ def parse_file(self, Data=[]):
+ '''
+ parse_file(Data=[0x12, 0x34, 0x56, 0x89]) -> dict(file)
+ mainly based on the ISO7816 parsing style
+
+ 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 102.221
+ works over the UICC file structure (quite different from e.g. SIM card)
+ '''
+ # First ISO7816 parsing
+ fil = ISO7816.parse_file(self, Data)
+
+ # Then UICC extra attributes parsing
+ if 0xC6 in fil.keys():
+ fil = self.parse_pin_status(fil[0xC6], fil)
+ del fil[0xC6]
+
+ if 'File Identifier' in fil.keys():
+ for ref in self.files:
+ if fil['File Identifier'] == ref[0]:
+ fil['Name'] = ref[2]
+
+ # return the enriched file
+ return fil
+
+ @staticmethod
+ def parse_pin_status(Data, fil):
+ '''
+ parses a list of bytes provided in Data
+ interprets the content as the UICC pin status
+ and enriches the file dictionnary passed as argument
+ '''
+ PS_DO = Data[2:2+Data[1]]
+ Data = Data[2+len(PS_DO):]
+ PIN_status = ''
+ while len(Data) > 0:
+ [T, L, V] = first_TLV_parser(Data)
+ assert( T in (0x83, 0x95) )
+ if T == 0x95: # PIN usage
+ if (V[0] << 7) & 1:
+ PIN_status += '#use verification / encipherment ' \
+ '/ external authentication: '
+ elif (V[0] << 6) & 1:
+ PIN_status += '#use computation / decipherment ' \
+ '/ internal authentication: '
+ elif (V[0] << 5) & 1:
+ PIN_status += '#use SM response: '
+ elif (V[0] << 4) & 1:
+ PIN_status += '#use SM command: '
+ elif (V[0] << 3) & 1:
+ PIN_status += '#use PIN verification: '
+ elif (V[0] << 3) & 1:
+ PIN_status += '#use biometric user verification: '
+ elif V[0] == 0:
+ PIN_status += '#verification not required: '
+ elif T == 0x83: # PIN status
+ if len(PIN_status) == 0: PIN_status = '#'
+ if 0x00 < V[0] < 0x12 or 0x81 <= V[0] < 0x90:
+ PIN_status += UICC.pin_status[V[0]] + '#'
+ elif 0x12 <= V[0] < 0x1E:
+ PIN_status += 'RFU (Global)#'
+ elif 0x90 <= V[0] < 0x9F:
+ PIN_status += 'RFU (Local)#'
+ else:
+ PIN_status += '#'
+ #if self.dbg >= 2:
+ # print '[DBG] %s: %s; PIN status: %s' % (T, V, PIN_status)
+ Data = Data[L+2:]
+ fil['PIN Status'] = PIN_status
+ return fil
+
+ def get_AID(self):
+ '''
+ checks EF_DIR at the MF level,
+ and available AID (Application ID) referenced
+
+ puts it into self.AID
+ interprets and print the content of the self.AID list
+ '''
+ #go back to MF and select EF_DIR
+ #self.select(Data=[])
+
+ # EF_DIR is at the MF level and contains Application ID:
+ EF_DIR = self.select([0x2F, 0x00], typ='pmf')
+ if self.dbg:
+ print '[DBG] EF_DIR: %s' % EF_DIR
+ if EF_DIR is None:
+ return None
+
+ # EF_DIR is an EF with linear fixed structure: contains records:
+ for rec in EF_DIR['Data']:
+ # check for a (new) AID:
+ if (rec[0], rec[2]) == (0x61, 0x4F) and len(rec) > 6 \
+ and rec[4:4+rec[3]] not in self.AID:
+ self.AID.append( rec[4:4+rec[3]] )
+
+ i = 1
+ for aid in self.AID:
+ aid_rid = tuple(aid[0:5])
+ aid_app = tuple(aid[5:7])
+ aid_country = tuple(aid[7:9])
+ aid_provider = tuple(aid[9:11])
+
+ # get AID application code, depending on SDO...
+ if aid_rid == (0xA0, 0x00, 0x00, 0x00, 0x09) \
+ and aid_app in self.ETSI_AID_app_code.keys():
+ aid_app = self.ETSI_AID_app_code[aid_app]
+ if aid_rid == (0xA0, 0x00, 0x00, 0x00, 0x87) \
+ and aid_app in self.GPP_AID_app_code.keys():
+ aid_app = self.GPP_AID_app_code[aid_app]
+ if aid_rid == (0xA0, 0x00, 0x00, 0x03, 0x43) \
+ and aid_app in self.GPP2_AID_app_code.keys():
+ aid_app = self.GPP2_AID_app_code[aid_app]
+ # get AID responsible SDO and country
+ if aid_rid in self.AID_RID.keys(): aid_rid = self.AID_RID[aid_rid]
+ if aid_country in self.AID_country_code.keys():
+ aid_country = self.AID_country_code[aid_country]
+
+ print 'found [AID %s] %s || %s || %s || %s || %s' \
+ % (i, aid_rid, aid_app, aid_country, \
+ aid_provider, tuple(aid[11:]) )
+ i += 1
+
+ def get_ICCID(self):
+ '''
+ check EF_ICCID at the MF level,
+ and returnq the ASCII value of the ICCID
+ '''
+ #go back to MF and select EF_ICCID
+ #self.select(Data=[])
+
+ # EF_ICCID is at the MF level and contains Application ID:
+ EF_ICCID = self.select([0x2F, 0xE2], typ='pmf')
+ if self.dbg:
+ print '[DBG] EF_ICCID: %s' % EF_ICCID
+ if EF_ICCID is None:
+ return None
+ return decode_BCD( EF_ICCID['Data'] )
+
+ def select_by_name(self, name=''):
+ '''
+ AID selection by name taken from UICC.files
+ '''
+ for i in range(len(self.files)):
+ if name == self.files[i][2]:
+ return self.select( self.files[i][0], 'pmf' )
+
+ def select_by_aid(self, aid_num=1):
+ '''
+ AID selection by index
+ '''
+ if len(self.AID) != 0:
+ return self.select(self.AID[aid_num-1], 'aid')
+
+
+
diff --git a/card/SIM.py b/card/SIM.py
new file mode 100644
index 0000000..1ad45e3
--- /dev/null
+++ b/card/SIM.py
@@ -0,0 +1,274 @@
+"""
+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
+from card.utils import *
+
+
+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:
+ print '[DBG] type definition: %s' % type(self)
+ print '[DBG] CLA definition: %s' % hex(self.CLA)
+
+
+ def sw_status(self, 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(self, 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:
+ print '[WNG] bad 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:
+ print '[WNG] bad 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!
+
+ unblock CHV1 (PIN code) or CHV2 with UNBLOCK_CHV APDU command
+ and set 0000 value for new PIN
+ call ISO7816 UNBLOCK_CHV method
+ '''
+ print 'not correctly implemented'
+ 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:
+ print '[WNG] bad 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
+
+ run GSM authentication algorithm:
+ accepts any kind of RAND (old GSM fashion)
+ feed with RAND 16 bytes value
+ return a list with SRES and Kc, or None on error
+ '''
+ if len(RAND) != 16:
+ if self.dbg:
+ print '[WNG] needs a 16 bytes input RAND value'
+ return None
+ # select DF_GSM directory
+ self.select([0x7F, 0x20])
+ if self.coms()[2] != (0x90, 0x00):
+ if self.dbg:
+ print '[DBG] %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:
+ print '[DBG] %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:
+ print '[DBG] %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:
+ print '[DBG] %s' % self.coms()
+ return None
+
+ # select IMSI file
+ imsi = self.select([0x6F, 0x07])
+ if self.coms()[2] != (0x90, 0x00):
+ if self.dbg:
+ print '[DBG] %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:
+ print '[DBG] %s' % self.coms()
+ return None
+
+
diff --git a/card/USIM.py b/card/USIM.py
new file mode 100644
index 0000000..d8cbb85
--- /dev/null
+++ b/card/USIM.py
@@ -0,0 +1,402 @@
+"""
+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.FS import *
+from card.utils import *
+
+
+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:
+ print '[DBG] type definition: %s' % type(self)
+ print '[DBG] CLA definition: %s' % hex(self.CLA)
+
+ # USIM selection from AID
+ print '[+] 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( Data=aid, typ='aid')
+ if usim is None:
+ print '[+] USIM AID selection failed'
+ else:
+ print '[+] USIM AID selection succeeded\n'
+
+ 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:
+ print '[DBG] %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])
+ print '[+] 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] )
+ print '[+] 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'] )
+ print '[+] 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:
+ print '[+] 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 > 1:
+ print '[DBG] %s' % self.coms()
+ if self.coms()[2] == 0x90 and self.dbg:
+ print '[+] Successful GBA_BP update with B-TID ' \
+ 'and key lifetime'
+ if self.dbg > 2:
+ print '[DBG] new value of EF_GBA_BP:\n%s' \
+ % self.get_GBA_BP()
+ else:
+ if self.dbg:
+ print '[+] 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] )
+
+ print '[+] 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:
+ if self.dbg:
+ print '[WNG] authenticate: bad parameters'
+ return None
+
+ inp = []
+ if ctx == '3G':
+ P2 = 0x81
+ elif ctx == 'VGCS':
+ P2 = 0x82
+ print '[+] Not implemented. Exit.'
+ return None
+ elif ctx == 'MBMS':
+ print '[+] Not implemented. Exit.'
+ 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:
+ if self.dbg:
+ print '[WNG] bad parameters'
+ 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:
+ print '[+] 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:
+ print '[+] Successful 3G authentication. ' \
+ 'Get [RES, CK, IK(, Kc)]'
+ elif P2 == 0x84 and self.dbg:
+ print '[+] 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:
+ print '[+] Synchronization failure. Get [AUTS]'
+ values = LV_parser(val[1:])
+ return values
+ #else:
+ if self.dbg:
+ print '[+] authentication 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:
+ print '[+] Successful GBA derivation. Get [Ks_EXT_NAF]'
+ values = LV_parser(val[1:])
+ return values
+ if self.dbg:
+ print '[DBG] authentication failure: %s' % self.coms()
+ return None
+
+ def bf_FS_from_init( self, filename='bf_USIM', file_dict=USIM_app_FS,
+ init_method='select_by_aid', init_args=[1] ):
+ '''
+ bruteforces the USIM filesystem at the application initialization level:
+ thanks to UICC.select_by_aid(1)
+ could be used another way...
+ stores the result in the file passed in argument
+ (file will be overwritten)
+
+ TODO: does not manage file recursivity (when entering DF)
+ only scan 1st level files
+ '''
+ fd = open(filename, 'w')
+ # loop on all possible addresses
+ for i in range(0x00, 0xff):
+ for j in range(0x00, 0xff):
+ # here "ret" is useless,
+ # but calling the method places the smartcard
+ # at the right address (by default: 1st AID)
+ ret = getattr(self, init_method)(*init_args)
+ fil = self.select( [i, j] )
+ # if file characteristics is readable
+ if fil is not None:
+ if self.dbg:
+ print '[+] USIM file found at address %s %s' % (i, j)
+ # warning when working with DF recursivity
+ if (i, j) in file_dict.keys():
+ fil['name'] = file_dict[(i, j)][0]
+ k = fil.keys()
+ k.sort()
+ fd.write('\n')
+ for key in k:
+ fd.write('%s: %s\n' % (key, fil[key]))
+ # if file exists but special conditions are returned
+ if self.coms()[2] not in ((0x6A, 0x82), (0x90, 0x00)):
+ if self.dbg:
+ print '[+] special condition %s when selecting' \
+ ' / reading USIM file at address %s %s' \
+ % ( self.coms()[2], i, j )
+ fd.write('\n')
+ fd.write('file exists at address: %s %s\n' \
+ % (hex(i)[2:], hex(j)[2:]))
+ fd.write('%s\n' % self.coms()[1])
+
+ if self.dbg and (i % 0x10 == 0 and j % 0x40 == 0):
+ print '[-] going over address %s %s' % (i, j)
+
+ fd.write('\n')
+ fd.close()
+
+ def bf_FS_from_MF(self, filename):
+ '''
+ not implemented
+ '''
+ #bf from MF, and recursively under each DF
+ #bf from each AID and recursively under each DF in AID
+ pass
+
+
diff --git a/card/__init__.py b/card/__init__.py
new file mode 100644
index 0000000..c5bafa0
--- /dev/null
+++ b/card/__init__.py
@@ -0,0 +1,27 @@
+"""
+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 and Jean-Daniel Aussel pyscard binding
+# specificities of SIM and USIM card available
+
+
+__all__ = ['utils', 'ICC', 'SIM', 'USIM', 'FS']
+__version__ = '0.1.0'
+
diff --git a/card/utils.py b/card/utils.py
new file mode 100644
index 0000000..05e7a9e
--- /dev/null
+++ b/card/utils.py
@@ -0,0 +1,252 @@
+"""
+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
+
+# 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
+ if bytelist[i+1] > 0x50:
+ Len_num = bytelist[i+1] - 0x50
+ Len_bytes = bytelist[i+2:i+1+Len_num]
+ Len = 0
+ for j in range(len(Len_bytes)):
+ Len += bytelist[i+1+Len_num-j] * pow(256, j)
+ Val = bytelist[i+1+Len_num:i+1+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:
+ string += str( B & 0x0F )
+ string += str( B >> 4 )
+ return string
+
+
+#######################################################
+# 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
+ '''
+ return self.apdu_stack[-1]
+
diff --git a/osmo-sim-auth.py b/osmo-sim-auth.py
new file mode 100755
index 0000000..a1364ab
--- /dev/null
+++ b/osmo-sim-auth.py
@@ -0,0 +1,106 @@
+#!/usr/bin/python
+
+"""
+Test script for (U)SIM authentication
+Copyright (C) 2011 Harald Welte <laforge@gnumonks.org>
+
+based heavily on the "card" library by Benoit Michau and pyscard
+
+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.
+"""
+
+from binascii import *
+from card.utils import *
+from optparse import OptionParser
+from card.USIM import USIM
+from card.SIM import SIM
+
+def handle_usim(options, rand_bin, autn_bin):
+ u = USIM()
+ if not u:
+ print "Error opening USIM"
+ exit(1)
+
+ if options.debug:
+ u.dbg = 2;
+
+ imsi = u.get_imsi()
+ print "Testing USIM card with IMSI %s" % imsi
+
+ print "\nUMTS Authentication"
+ ret = u.authenticate(rand_bin, autn_bin, ctx='3G')
+ if len(ret) == 1:
+ print "AUTS:\t%s" % b2a_hex(byteToString(ret[0]))
+ else:
+ print "RES:\t%s" % b2a_hex(byteToString(ret[0]))
+ print "CK:\t%s" % b2a_hex(byteToString(ret[1]))
+ print "IK:\t%s" % b2a_hex(byteToString(ret[2]))
+ if len(ret) == 4:
+ print "Kc:\t%s" % b2a_hex(byteToString(ret[3]))
+
+ print "\nGSM Authentication"
+ ret = u.authenticate(rand_bin, autn_bin, ctx='2G')
+ if not len(ret) == 2:
+ print "Error during 2G authentication"
+ exit(1)
+ print "SRES:\t%s" % b2a_hex(byteToString(ret[0]))
+ print "Kc:\t%s" % b2a_hex(byteToString(ret[1]))
+
+def handle_sim(options, rand_bin):
+ s= SIM()
+ if not s:
+ print "Error opening SIM"
+ exit(1)
+
+ imsi = s.get_imsi()
+ print "Testing SIM card with IMSI %s" % imsi
+
+ print "\nGSM Authentication"
+ ret = s.run_gsm_alg(rand_bin)
+ print "SRES:\t%s" % b2a_hex(byteToString(ret[0]))
+ print "Kc:\t%s" % b2a_hex(byteToString(ret[1]))
+
+
+if __name__ == "__main__":
+ parser = OptionParser()
+ parser.add_option("-a", "--autn", dest="autn",
+ help="AUTN parameter from AuC")
+ parser.add_option("-r", "--rand", dest="rand",
+ help="RAND parameter from AuC")
+ parser.add_option("-d", "--debug", dest="debug",
+ help="Enable debug output",
+ action="store_true")
+ parser.add_option("-s", "--sim", dest="sim",
+ help="SIM mode (default: USIM)",
+ action="store_true", default=False)
+
+ (options, args) = parser.parse_args()
+
+ if not options.rand:
+ print "You have to specify RAND"
+ exit(2)
+
+ rand_bin = stringToByte(a2b_hex(options.rand))
+ if options.autn:
+ autn_bin = stringToByte(a2b_hex(options.autn))
+
+ if options.sim == True:
+ handle_sim(options, rand_bin)
+ else:
+ if not options.autn:
+ print "You have to specify AUTN"
+ exit(2)
+ handle_usim(options, rand_bin, autn_bin)
+