Initial check-in of a small command line based tool for (U)SIM auth
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)
+