diff options
Diffstat (limited to 'card/utils.py')
-rw-r--r-- | card/utils.py | 252 |
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] + |