aboutsummaryrefslogtreecommitdiffstats
path: root/card/utils.py
diff options
context:
space:
mode:
Diffstat (limited to 'card/utils.py')
-rw-r--r--card/utils.py252
1 files changed, 252 insertions, 0 deletions
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]
+