Sync 'card' library with upstream
This reflects 2a81963790e27eb6b188359af169c45afb6d3aaf of https://github.com/mitshell/card.git
This commit is contained in:
parent
76c74c93d5
commit
de31d9f88b
301
card/ICC.py
301
card/ICC.py
|
@ -35,10 +35,8 @@ import re
|
||||||
|
|
||||||
# smartcard python modules from pyscard
|
# smartcard python modules from pyscard
|
||||||
from smartcard.CardType import AnyCardType
|
from smartcard.CardType import AnyCardType
|
||||||
from smartcard.CardType import ATRCardType
|
|
||||||
from smartcard.CardRequest import CardRequest
|
from smartcard.CardRequest import CardRequest
|
||||||
from smartcard.CardConnection import CardConnection
|
from smartcard.CardConnection import CardConnection
|
||||||
from smartcard.CardConnectionObserver import ConsoleCardConnectionObserver
|
|
||||||
from smartcard.ATR import ATR
|
from smartcard.ATR import ATR
|
||||||
from smartcard.Exceptions import CardConnectionException
|
from smartcard.Exceptions import CardConnectionException
|
||||||
from smartcard.util import toHexString
|
from smartcard.util import toHexString
|
||||||
|
@ -51,13 +49,13 @@ from card.utils import *
|
||||||
###########################################################
|
###########################################################
|
||||||
|
|
||||||
class ISO7816(object):
|
class ISO7816(object):
|
||||||
'''
|
"""
|
||||||
define attributes, methods and facilities for ISO-7816-4 standard smartcard
|
define attributes, methods and facilities for ISO-7816-4 standard smartcard
|
||||||
|
|
||||||
use self.dbg = 1 or more to print live debugging information
|
use self.dbg = 1 or more to print live debugging information
|
||||||
standard instructions codes available in "INS_dic" class dictionnary
|
standard instructions codes available in "INS_dic" class dictionnary
|
||||||
standard file tags available in "file_tags" class dictionnary
|
standard file tags available in "file_tags" class dictionnary
|
||||||
'''
|
"""
|
||||||
|
|
||||||
dbg = 0
|
dbg = 0
|
||||||
|
|
||||||
|
@ -103,7 +101,7 @@ class ISO7816(object):
|
||||||
0xC0 : 'GET RESPONSE',
|
0xC0 : 'GET RESPONSE',
|
||||||
0xC2 : 'ENVELOPE',
|
0xC2 : 'ENVELOPE',
|
||||||
0xC3 : 'ENVELOPE',
|
0xC3 : 'ENVELOPE',
|
||||||
0xCA : 'RETRIEVE DATA',
|
0xCA : 'GET DATA',
|
||||||
0xCB : 'RETRIEVE DATA',
|
0xCB : 'RETRIEVE DATA',
|
||||||
0xD2 : 'WRITE RECORD',
|
0xD2 : 'WRITE RECORD',
|
||||||
0xD6 : 'UPDATE BINARY',
|
0xD6 : 'UPDATE BINARY',
|
||||||
|
@ -143,54 +141,46 @@ class ISO7816(object):
|
||||||
0xAB : 'Security Attribute expanded',
|
0xAB : 'Security Attribute expanded',
|
||||||
}
|
}
|
||||||
|
|
||||||
def __init__(self, atr=None, CLA=0x00):
|
def __init__(self, CLA=0x00):
|
||||||
'''
|
"""
|
||||||
connect smartcard and defines class CLA code for communication
|
connect smartcard and defines class CLA code for communication
|
||||||
uses "pyscard" library services
|
uses "pyscard" library services
|
||||||
|
|
||||||
creates self.CLA attribute with CLA code
|
creates self.CLA attribute with CLA code
|
||||||
and self.coms attribute with associated "apdu_stack" instance
|
and self.coms attribute with associated "apdu_stack" instance
|
||||||
'''
|
"""
|
||||||
|
|
||||||
if (atr):
|
|
||||||
cardtype = ATRCardType(atr)
|
|
||||||
else:
|
|
||||||
cardtype = AnyCardType()
|
cardtype = AnyCardType()
|
||||||
|
|
||||||
cardrequest = CardRequest(timeout=1, cardType=cardtype)
|
cardrequest = CardRequest(timeout=1, cardType=cardtype)
|
||||||
self.cardservice = cardrequest.waitforcard()
|
self.cardservice = cardrequest.waitforcard()
|
||||||
self.cardservice.connection.connect()
|
self.cardservice.connection.connect()
|
||||||
self.reader = self.cardservice.connection.getReader()
|
self.reader = self.cardservice.connection.getReader()
|
||||||
self.ATR = self.cardservice.connection.getATR()
|
self.ATR = self.cardservice.connection.getATR()
|
||||||
|
|
||||||
#observer = ConsoleCardConnectionObserver()
|
|
||||||
#self.cardservice.connection.addObserver(observer)
|
|
||||||
|
|
||||||
self.CLA = CLA
|
self.CLA = CLA
|
||||||
self.coms = apdu_stack()
|
self.coms = apdu_stack()
|
||||||
|
|
||||||
def disconnect(self):
|
def disconnect(self):
|
||||||
'''
|
"""
|
||||||
disconnect smartcard: stops the session
|
disconnect smartcard: stops the session
|
||||||
uses "pyscard" library service
|
uses "pyscard" library service
|
||||||
'''
|
"""
|
||||||
self.cardservice.connection.disconnect()
|
self.cardservice.connection.disconnect()
|
||||||
|
|
||||||
def define_class(self, CLA=0x00):
|
def define_class(self, CLA=0x00):
|
||||||
'''
|
"""
|
||||||
define smartcard class attribute for APDU command
|
define smartcard class attribute for APDU command
|
||||||
override CLA value defined in class initialization
|
override CLA value defined in class initialization
|
||||||
'''
|
"""
|
||||||
self.CLA = CLA
|
self.CLA = CLA
|
||||||
|
|
||||||
def ATR_scan(self, smlist_file="/usr/share/pcsc/smartcard_list.txt"):
|
def ATR_scan(self, smlist_file="/usr/share/pcsc/smartcard_list.txt"):
|
||||||
'''
|
"""
|
||||||
print smartcard info retrieved from AnswerToReset
|
print smartcard info retrieved from AnswerToReset
|
||||||
thanks to pyscard routine
|
thanks to pyscard routine
|
||||||
|
|
||||||
if pcsc_scan is installed,
|
if pcsc_scan is installed,
|
||||||
use the signature file passed as argument for guessing the card
|
use the signature file passed as argument for guessing the card
|
||||||
'''
|
"""
|
||||||
print('\nsmartcard reader: %s' % self.reader)
|
print('\nsmartcard reader: %s' % self.reader)
|
||||||
if self.ATR != None:
|
if self.ATR != None:
|
||||||
print('\nsmart card ATR is: %s' % toHexString(self.ATR))
|
print('\nsmart card ATR is: %s' % toHexString(self.ATR))
|
||||||
|
@ -225,12 +215,12 @@ class ISO7816(object):
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def sw_status(sw1, sw2):
|
def sw_status(sw1, sw2):
|
||||||
'''
|
"""
|
||||||
sw_status(sw1=int, sw2=int) -> string
|
sw_status(sw1=int, sw2=int) -> string
|
||||||
|
|
||||||
SW status bytes interpretation as defined in ISO-7816 part 4 standard
|
SW status bytes interpretation as defined in ISO-7816 part 4 standard
|
||||||
helps to speak and understand with the smartcard!
|
helps to speak and understand with the smartcard!
|
||||||
'''
|
"""
|
||||||
status = 'undefined status'
|
status = 'undefined status'
|
||||||
if sw1 == 0x90 and sw2 == 0x00: status = 'normal processing: ' \
|
if sw1 == 0x90 and sw2 == 0x00: status = 'normal processing: ' \
|
||||||
'command accepted: no further qualification'
|
'command accepted: no further qualification'
|
||||||
|
@ -326,7 +316,7 @@ class ISO7816(object):
|
||||||
return status
|
return status
|
||||||
|
|
||||||
def sr_apdu(self, apdu, force=False):
|
def sr_apdu(self, apdu, force=False):
|
||||||
'''
|
"""
|
||||||
sr_apdu(apdu=[0x.., 0x.., ...]) ->
|
sr_apdu(apdu=[0x.., 0x.., ...]) ->
|
||||||
list [ string(apdu sent information),
|
list [ string(apdu sent information),
|
||||||
string(SW codes interpretation),
|
string(SW codes interpretation),
|
||||||
|
@ -335,7 +325,7 @@ class ISO7816(object):
|
||||||
|
|
||||||
generic function to send apdu, receive and interpret response
|
generic function to send apdu, receive and interpret response
|
||||||
force: force card reconnection if pyscard transmission fails
|
force: force card reconnection if pyscard transmission fails
|
||||||
'''
|
"""
|
||||||
if force:
|
if force:
|
||||||
try:
|
try:
|
||||||
data, sw1, sw2 = self.cardservice.connection.transmit(apdu)
|
data, sw1, sw2 = self.cardservice.connection.transmit(apdu)
|
||||||
|
@ -356,7 +346,7 @@ class ISO7816(object):
|
||||||
data ]
|
data ]
|
||||||
|
|
||||||
def bf_cla(self, start=0, param=[0xA4, 0x00, 0x00, 0x02, 0x3F, 0x00]):
|
def bf_cla(self, start=0, param=[0xA4, 0x00, 0x00, 0x02, 0x3F, 0x00]):
|
||||||
'''
|
"""
|
||||||
bf_cla( start=int(starting CLA),
|
bf_cla( start=int(starting CLA),
|
||||||
param=list(bytes for selecting file 0x3F, 0x00) ) ->
|
param=list(bytes for selecting file 0x3F, 0x00) ) ->
|
||||||
list( CLA which could be supported )
|
list( CLA which could be supported )
|
||||||
|
@ -368,7 +358,7 @@ class ISO7816(object):
|
||||||
WARNING:
|
WARNING:
|
||||||
can block the card definitively
|
can block the card definitively
|
||||||
Do not do it with your own VISA / MASTERCARD
|
Do not do it with your own VISA / MASTERCARD
|
||||||
'''
|
"""
|
||||||
clist = []
|
clist = []
|
||||||
for i in range(start, 256):
|
for i in range(start, 256):
|
||||||
ret = self.sr_apdu([i] + param)
|
ret = self.sr_apdu([i] + param)
|
||||||
|
@ -379,7 +369,7 @@ class ISO7816(object):
|
||||||
return clist
|
return clist
|
||||||
|
|
||||||
def bf_ins(self, start=0):
|
def bf_ins(self, start=0):
|
||||||
'''
|
"""
|
||||||
bf_cla( start=int(starting INS) )
|
bf_cla( start=int(starting INS) )
|
||||||
-> list( INS which could be supported )
|
-> list( INS which could be supported )
|
||||||
|
|
||||||
|
@ -390,7 +380,7 @@ class ISO7816(object):
|
||||||
WARNING:
|
WARNING:
|
||||||
can block the card definitively
|
can block the card definitively
|
||||||
Do not do it with your own VISA / MASTERCARD
|
Do not do it with your own VISA / MASTERCARD
|
||||||
'''
|
"""
|
||||||
ilist = []
|
ilist = []
|
||||||
for i in range(start, 256):
|
for i in range(start, 256):
|
||||||
if self.dbg >= 3:
|
if self.dbg >= 3:
|
||||||
|
@ -409,43 +399,43 @@ class ISO7816(object):
|
||||||
# ISO 7816 and described further in ETSI 101.221
|
# ISO 7816 and described further in ETSI 101.221
|
||||||
###
|
###
|
||||||
def READ_BINARY(self, P1=0x00, P2=0x00, Le=0x01):
|
def READ_BINARY(self, P1=0x00, P2=0x00, Le=0x01):
|
||||||
'''
|
"""
|
||||||
APDU command to read the content of EF file with transparent structure
|
APDU command to read the content of EF file with transparent structure
|
||||||
Le: length of data bytes to be read
|
Le: length of data bytes to be read
|
||||||
|
|
||||||
call sr_apdu method
|
call sr_apdu method
|
||||||
'''
|
"""
|
||||||
READ_BINARY = [self.CLA, 0xB0, P1, P2, Le]
|
READ_BINARY = [self.CLA, 0xB0, P1, P2, Le]
|
||||||
return self.sr_apdu(READ_BINARY)
|
return self.sr_apdu(READ_BINARY)
|
||||||
|
|
||||||
def WRITE_BINARY(self, P1=0x00, P2=0x00, Data=[]):
|
def WRITE_BINARY(self, P1=0x00, P2=0x00, Data=[]):
|
||||||
'''
|
"""
|
||||||
APDU command to write the content of EF file with transparent structure
|
APDU command to write the content of EF file with transparent structure
|
||||||
|
|
||||||
Data: list of data bytes to be written
|
Data: list of data bytes to be written
|
||||||
call sr_apdu method
|
call sr_apdu method
|
||||||
'''
|
"""
|
||||||
WRITE_BINARY = [self.CLA, 0xD0, P1, P2, len(Data)] + Data
|
WRITE_BINARY = [self.CLA, 0xD0, P1, P2, len(Data)] + Data
|
||||||
return self.sr_apdu(WRITE_BINARY)
|
return self.sr_apdu(WRITE_BINARY)
|
||||||
|
|
||||||
def UPDATE_BINARY(self, P1=0x00, P2=0x00, Data=[]):
|
def UPDATE_BINARY(self, P1=0x00, P2=0x00, Data=[]):
|
||||||
'''
|
"""
|
||||||
APDU command to update the content of EF file with transparent structure
|
APDU command to update the content of EF file with transparent structure
|
||||||
|
|
||||||
Data: list of data bytes to be written
|
Data: list of data bytes to be written
|
||||||
call sr_apdu method
|
call sr_apdu method
|
||||||
'''
|
"""
|
||||||
UPDATE_BINARY = [self.CLA, 0xD6, P1, P2, len(Data)] + Data
|
UPDATE_BINARY = [self.CLA, 0xD6, P1, P2, len(Data)] + Data
|
||||||
return self.sr_apdu(UPDATE_BINARY)
|
return self.sr_apdu(UPDATE_BINARY)
|
||||||
|
|
||||||
def ERASE_BINARY(self, P1=0x00, P2=0x00, Lc=None, Data=[]):
|
def ERASE_BINARY(self, P1=0x00, P2=0x00, Lc=None, Data=[]):
|
||||||
'''
|
"""
|
||||||
APDU command to erase the content of EF file with transparent structure
|
APDU command to erase the content of EF file with transparent structure
|
||||||
|
|
||||||
Lc: 'None' or '0x02'
|
Lc: 'None' or '0x02'
|
||||||
Data: list of data bytes to be written
|
Data: list of data bytes to be written
|
||||||
call sr_apdu method
|
call sr_apdu method
|
||||||
'''
|
"""
|
||||||
if Lc is None:
|
if Lc is None:
|
||||||
ERASE_BINARY = [self.CLA, 0x0E, P1, P2]
|
ERASE_BINARY = [self.CLA, 0x0E, P1, P2]
|
||||||
else:
|
else:
|
||||||
|
@ -453,71 +443,71 @@ class ISO7816(object):
|
||||||
return self.sr_apdu(ERASE_BINARY)
|
return self.sr_apdu(ERASE_BINARY)
|
||||||
|
|
||||||
def READ_RECORD(self, P1=0x00, P2=0x00, Le=0x00):
|
def READ_RECORD(self, P1=0x00, P2=0x00, Le=0x00):
|
||||||
'''
|
"""
|
||||||
APDU command to read the content of EF file with record structure
|
APDU command to read the content of EF file with record structure
|
||||||
|
|
||||||
P1: record number
|
P1: record number
|
||||||
P2: reference control
|
P2: reference control
|
||||||
Le: length of data bytes to be read
|
Le: length of data bytes to be read
|
||||||
call sr_apdu method
|
call sr_apdu method
|
||||||
'''
|
"""
|
||||||
READ_RECORD = [self.CLA, 0xB2, P1, P2, Le]
|
READ_RECORD = [self.CLA, 0xB2, P1, P2, Le]
|
||||||
return self.sr_apdu(READ_RECORD)
|
return self.sr_apdu(READ_RECORD)
|
||||||
|
|
||||||
def WRITE_RECORD(self, P1=0x00, P2=0x00, Data=[]):
|
def WRITE_RECORD(self, P1=0x00, P2=0x00, Data=[]):
|
||||||
'''
|
"""
|
||||||
APDU command to write the content of EF file with record structure
|
APDU command to write the content of EF file with record structure
|
||||||
|
|
||||||
P1: record number
|
P1: record number
|
||||||
P2: reference control
|
P2: reference control
|
||||||
Data: list of data bytes to be written in the record
|
Data: list of data bytes to be written in the record
|
||||||
call sr_apdu method
|
call sr_apdu method
|
||||||
'''
|
"""
|
||||||
WRITE_RECORD = [self.CLA, 0xD2, P1, P2, len(Data)] + Data
|
WRITE_RECORD = [self.CLA, 0xD2, P1, P2, len(Data)] + Data
|
||||||
return self.sr_apdu(WRITE_RECORD)
|
return self.sr_apdu(WRITE_RECORD)
|
||||||
|
|
||||||
def APPEND_RECORD(self, P2=0x00, Data=[]):
|
def APPEND_RECORD(self, P2=0x00, Data=[]):
|
||||||
'''
|
"""
|
||||||
APDU command to append a record on EF file with record structure
|
APDU command to append a record on EF file with record structure
|
||||||
|
|
||||||
P2: reference control
|
P2: reference control
|
||||||
Data: list of data bytes to be appended on the record
|
Data: list of data bytes to be appended on the record
|
||||||
call sr_apdu method
|
call sr_apdu method
|
||||||
'''
|
"""
|
||||||
APPEND_RECORD = [self.CLA, 0xE2, 0x00, P2, len(Data)] + Data
|
APPEND_RECORD = [self.CLA, 0xE2, 0x00, P2, len(Data)] + Data
|
||||||
return self.sr_apdu(APPEND_RECORD)
|
return self.sr_apdu(APPEND_RECORD)
|
||||||
|
|
||||||
def UPDATE_RECORD(self, P1=0x00, P2=0x00, Data=[]):
|
def UPDATE_RECORD(self, P1=0x00, P2=0x00, Data=[]):
|
||||||
'''
|
"""
|
||||||
APDU command to update the content of EF file with record structure
|
APDU command to update the content of EF file with record structure
|
||||||
|
|
||||||
P1: record number
|
P1: record number
|
||||||
P2: reference control
|
P2: reference control
|
||||||
Data: list of data bytes to update the record
|
Data: list of data bytes to update the record
|
||||||
call sr_apdu method
|
call sr_apdu method
|
||||||
'''
|
"""
|
||||||
APPEND_RECORD = [self.CLA, 0xDC, P1, P2, len(Data)] + Data
|
APPEND_RECORD = [self.CLA, 0xDC, P1, P2, len(Data)] + Data
|
||||||
return self.sr_apdu(APPEND_RECORD)
|
return self.sr_apdu(APPEND_RECORD)
|
||||||
|
|
||||||
def GET_DATA(self, P1=0x00, P2=0x00, Le=0x01):
|
def GET_DATA(self, P1=0x00, P2=0x00, Le=0x01):
|
||||||
'''
|
"""
|
||||||
APDU command to retrieve data object
|
APDU command to retrieve data object
|
||||||
|
|
||||||
P1 and P2: reference control for data object description
|
P1 and P2: reference control for data object description
|
||||||
Le: number of bytes expected in the response
|
Le: number of bytes expected in the response
|
||||||
call sr_apdu method
|
call sr_apdu method
|
||||||
'''
|
"""
|
||||||
GET_DATA = [self.CLA, 0xCA, P1, P2, Le]
|
GET_DATA = [self.CLA, 0xCA, P1, P2, Le]
|
||||||
return self.sr_apdu(GET_DATA)
|
return self.sr_apdu(GET_DATA)
|
||||||
|
|
||||||
def PUT_DATA(self, P1=0x00, P2=0x00, Data=[]):
|
def PUT_DATA(self, P1=0x00, P2=0x00, Data=[]):
|
||||||
'''
|
"""
|
||||||
APDU command to store data object
|
APDU command to store data object
|
||||||
|
|
||||||
P1 and P2: reference control for data object description
|
P1 and P2: reference control for data object description
|
||||||
Data: list of data bytes to put in the data object structure
|
Data: list of data bytes to put in the data object structure
|
||||||
call sr_apdu method
|
call sr_apdu method
|
||||||
'''
|
"""
|
||||||
if len(Data) == 0:
|
if len(Data) == 0:
|
||||||
PUT_DATA = [self.CLA, 0xDA, P1, P2]
|
PUT_DATA = [self.CLA, 0xDA, P1, P2]
|
||||||
elif 1 <= len(Data) <= 255:
|
elif 1 <= len(Data) <= 255:
|
||||||
|
@ -529,26 +519,26 @@ class ISO7816(object):
|
||||||
|
|
||||||
def SELECT_FILE(self, P1=0x00, P2=0x00, Data=[0x3F, 0x00], \
|
def SELECT_FILE(self, P1=0x00, P2=0x00, Data=[0x3F, 0x00], \
|
||||||
with_length=True):
|
with_length=True):
|
||||||
'''
|
"""
|
||||||
APDU command to select file
|
APDU command to select file
|
||||||
|
|
||||||
P1 and P2: selection control
|
P1 and P2: selection control
|
||||||
Data: list of bytes describing the file identifier or address
|
Data: list of bytes describing the file identifier or address
|
||||||
call sr_apdu method
|
call sr_apdu method
|
||||||
'''
|
"""
|
||||||
if with_length:
|
if with_length:
|
||||||
Data = [min(len(Data), 255)] + Data
|
Data = [min(len(Data), 255)] + Data
|
||||||
SELECT_FILE = [self.CLA, 0xA4, P1, P2] + Data
|
SELECT_FILE = [self.CLA, 0xA4, P1, P2] + Data
|
||||||
return self.sr_apdu(SELECT_FILE)
|
return self.sr_apdu(SELECT_FILE)
|
||||||
|
|
||||||
def VERIFY(self, P2=0x00, Data=[]):
|
def VERIFY(self, P2=0x00, Data=[]):
|
||||||
'''
|
"""
|
||||||
APDU command to verify user PIN, password or security codes
|
APDU command to verify user PIN, password or security codes
|
||||||
|
|
||||||
P2: reference control
|
P2: reference control
|
||||||
Data: list of bytes to be verified by the card
|
Data: list of bytes to be verified by the card
|
||||||
call sr_apdu method
|
call sr_apdu method
|
||||||
'''
|
"""
|
||||||
if len(Data) == 0:
|
if len(Data) == 0:
|
||||||
VERIFY = [self.CLA, 0x20, 0x00, P2]
|
VERIFY = [self.CLA, 0x20, 0x00, P2]
|
||||||
elif 1 <= len(Data) <= 255:
|
elif 1 <= len(Data) <= 255:
|
||||||
|
@ -559,25 +549,25 @@ class ISO7816(object):
|
||||||
return self.sr_apdu(VERIFY)
|
return self.sr_apdu(VERIFY)
|
||||||
|
|
||||||
def INTERNAL_AUTHENTICATE(self, P1=0x00, P2=0x00, Data=[]):
|
def INTERNAL_AUTHENTICATE(self, P1=0x00, P2=0x00, Data=[]):
|
||||||
'''
|
"""
|
||||||
APDU command to run internal authentication algorithm
|
APDU command to run internal authentication algorithm
|
||||||
|
|
||||||
P1 and P2: reference control (algo, secret key selection...)
|
P1 and P2: reference control (algo, secret key selection...)
|
||||||
Data: list of bytes containing the authentication challenge
|
Data: list of bytes containing the authentication challenge
|
||||||
call sr_apdu method
|
call sr_apdu method
|
||||||
'''
|
"""
|
||||||
INTERNAL_AUTHENTICATE = [self.CLA, 0x88, P1, P2, len(Data)] + Data
|
INTERNAL_AUTHENTICATE = [self.CLA, 0x88, P1, P2, len(Data)] + Data
|
||||||
return self.sr_apdu(INTERNAL_AUTHENTICATE)
|
return self.sr_apdu(INTERNAL_AUTHENTICATE)
|
||||||
|
|
||||||
def EXTERNAL_AUTHENTICATE(self, P1=0x00, P2=0x00, Data=[]):
|
def EXTERNAL_AUTHENTICATE(self, P1=0x00, P2=0x00, Data=[]):
|
||||||
'''
|
"""
|
||||||
APDU command to conditionally update the security status of the card
|
APDU command to conditionally update the security status of the card
|
||||||
after getting a challenge from it
|
after getting a challenge from it
|
||||||
|
|
||||||
P1 and P2: reference control (algo, secret key selection...)
|
P1 and P2: reference control (algo, secret key selection...)
|
||||||
Data: list of bytes containing the challenge response
|
Data: list of bytes containing the challenge response
|
||||||
call sr_apdu method
|
call sr_apdu method
|
||||||
'''
|
"""
|
||||||
if len(Data) == 0:
|
if len(Data) == 0:
|
||||||
EXTERNAL_AUTHENTICATE = [self.CLA, 0x82, P1, P2]
|
EXTERNAL_AUTHENTICATE = [self.CLA, 0x82, P1, P2]
|
||||||
elif 1 <= len(Data) <= 255:
|
elif 1 <= len(Data) <= 255:
|
||||||
|
@ -588,23 +578,23 @@ class ISO7816(object):
|
||||||
return self.sr_apdu(EXTERNAL_AUTHENTICATE)
|
return self.sr_apdu(EXTERNAL_AUTHENTICATE)
|
||||||
|
|
||||||
def GET_CHALLENGE(self):
|
def GET_CHALLENGE(self):
|
||||||
'''
|
"""
|
||||||
APDU command to get a challenge for external entity authentication
|
APDU command to get a challenge for external entity authentication
|
||||||
to the card
|
to the card
|
||||||
|
|
||||||
call sr_apdu method
|
call sr_apdu method
|
||||||
'''
|
"""
|
||||||
GET_CHALLENGE = [self.CLA, 0x84, 0x00, 0x00]
|
GET_CHALLENGE = [self.CLA, 0x84, 0x00, 0x00]
|
||||||
return self.sr_apdu(GET_CHALLENGE)
|
return self.sr_apdu(GET_CHALLENGE)
|
||||||
|
|
||||||
def MANAGE_CHANNEL(self, P1=0x00, P2=0x00):
|
def MANAGE_CHANNEL(self, P1=0x00, P2=0x00):
|
||||||
'''
|
"""
|
||||||
APDU to open and close supplementary logical channels
|
APDU to open and close supplementary logical channels
|
||||||
|
|
||||||
P1=0x00 to open, 0x80 to close
|
P1=0x00 to open, 0x80 to close
|
||||||
P2=0x00, 1, 2 or 3 to ask for logical channel number
|
P2=0x00, 1, 2 or 3 to ask for logical channel number
|
||||||
call sr_apdu method
|
call sr_apdu method
|
||||||
'''
|
"""
|
||||||
if (P1, P2) == (0x00, 0x00):
|
if (P1, P2) == (0x00, 0x00):
|
||||||
MANAGE_CHANNEL = [self.CLA, 0x70, P1, P2, 0x01]
|
MANAGE_CHANNEL = [self.CLA, 0x70, P1, P2, 0x01]
|
||||||
else:
|
else:
|
||||||
|
@ -612,24 +602,24 @@ class ISO7816(object):
|
||||||
return self.sr_apdu(MANAGE_CHANNEL)
|
return self.sr_apdu(MANAGE_CHANNEL)
|
||||||
|
|
||||||
def GET_RESPONSE(self, Le=0x01):
|
def GET_RESPONSE(self, Le=0x01):
|
||||||
'''
|
"""
|
||||||
APDU command to retrieve data after selection
|
APDU command to retrieve data after selection
|
||||||
or other kind of request that should get an extensive reply
|
or other kind of request that should get an extensive reply
|
||||||
|
|
||||||
Le: expected length of data
|
Le: expected length of data
|
||||||
call sr_apdu method
|
call sr_apdu method
|
||||||
'''
|
"""
|
||||||
GET_RESPONSE = [self.CLA, 0xC0, 0x00, 0x00, Le]
|
GET_RESPONSE = [self.CLA, 0xC0, 0x00, 0x00, Le]
|
||||||
return self.sr_apdu(GET_RESPONSE)
|
return self.sr_apdu(GET_RESPONSE)
|
||||||
|
|
||||||
def ENVELOPE(self, Data=[]):
|
def ENVELOPE(self, Data=[]):
|
||||||
'''
|
"""
|
||||||
APDU command to encapsulate data (APDU or other...)
|
APDU command to encapsulate data (APDU or other...)
|
||||||
check ETSI TS 102.221 for some examples...
|
check ETSI TS 102.221 for some examples...
|
||||||
|
|
||||||
Data: list of bytes
|
Data: list of bytes
|
||||||
call sr_apdu method
|
call sr_apdu method
|
||||||
'''
|
"""
|
||||||
if len(Data) == 0:
|
if len(Data) == 0:
|
||||||
ENVELOPE = [self.CLA, 0xC2, 0x00, 0x00]
|
ENVELOPE = [self.CLA, 0xC2, 0x00, 0x00]
|
||||||
elif 1 <= len(Data) <= 255:
|
elif 1 <= len(Data) <= 255:
|
||||||
|
@ -637,7 +627,7 @@ class ISO7816(object):
|
||||||
return self.sr_apdu(ENVELOPE)
|
return self.sr_apdu(ENVELOPE)
|
||||||
|
|
||||||
def SEARCH_RECORD(self, P1=0x00, P2=0x00, Data=[]):
|
def SEARCH_RECORD(self, P1=0x00, P2=0x00, Data=[]):
|
||||||
'''
|
"""
|
||||||
APDU command to seach pattern in the current EF file
|
APDU command to seach pattern in the current EF file
|
||||||
with record structure
|
with record structure
|
||||||
|
|
||||||
|
@ -645,36 +635,36 @@ class ISO7816(object):
|
||||||
P2: type of search
|
P2: type of search
|
||||||
Data: list of bytes describing a pattern to search for
|
Data: list of bytes describing a pattern to search for
|
||||||
call sr_apdu method
|
call sr_apdu method
|
||||||
'''
|
"""
|
||||||
SEARCH_RECORD = [self.CLA, 0xA2, P1, P2, len(Data)] + Data
|
SEARCH_RECORD = [self.CLA, 0xA2, P1, P2, len(Data)] + Data
|
||||||
return self.sr_apdu(SEARCH_RECORD)
|
return self.sr_apdu(SEARCH_RECORD)
|
||||||
|
|
||||||
def DISABLE_CHV(self, P1=0x00, P2=0x00, Data=[]):
|
def DISABLE_CHV(self, P1=0x00, P2=0x00, Data=[]):
|
||||||
'''
|
"""
|
||||||
APDU command to disable CHV verification (such as PIN or password...)
|
APDU command to disable CHV verification (such as PIN or password...)
|
||||||
|
|
||||||
P1: let to 0x00... or read ISO and ETSI specifications
|
P1: let to 0x00... or read ISO and ETSI specifications
|
||||||
P2: type of CHV to disable
|
P2: type of CHV to disable
|
||||||
Data: list of bytes for CHV value
|
Data: list of bytes for CHV value
|
||||||
call sr_apdu method
|
call sr_apdu method
|
||||||
'''
|
"""
|
||||||
DISABLE_CHV = [self.CLA, 0x26, P1, P2, len(Data)] + Data
|
DISABLE_CHV = [self.CLA, 0x26, P1, P2, len(Data)] + Data
|
||||||
return self.sr_apdu(DISABLE_CHV)
|
return self.sr_apdu(DISABLE_CHV)
|
||||||
|
|
||||||
def ENABLE_CHV(self, P1=0x00, P2=0x00, Data=[]):
|
def ENABLE_CHV(self, P1=0x00, P2=0x00, Data=[]):
|
||||||
'''
|
"""
|
||||||
APDU command to enable CHV verification (such as PIN or password...)
|
APDU command to enable CHV verification (such as PIN or password...)
|
||||||
|
|
||||||
P1: let to 0x00... or read ISO and ETSI specifications
|
P1: let to 0x00... or read ISO and ETSI specifications
|
||||||
P2: type of CHV to enable
|
P2: type of CHV to enable
|
||||||
Data: list of bytes for CHV value
|
Data: list of bytes for CHV value
|
||||||
call sr_apdu method
|
call sr_apdu method
|
||||||
'''
|
"""
|
||||||
ENABLE_CHV = [self.CLA, 0x28, P1, P2, len(Data)] + Data
|
ENABLE_CHV = [self.CLA, 0x28, P1, P2, len(Data)] + Data
|
||||||
return self.sr_apdu(ENABLE_CHV)
|
return self.sr_apdu(ENABLE_CHV)
|
||||||
|
|
||||||
def UNBLOCK_CHV(self, P2=0x00, Data=[]):
|
def UNBLOCK_CHV(self, P2=0x00, Data=[]):
|
||||||
'''
|
"""
|
||||||
APDU command to unblock CHV code (e.g. with PUK for deblocking PIN)
|
APDU command to unblock CHV code (e.g. with PUK for deblocking PIN)
|
||||||
|
|
||||||
P2: type of CHV to unblock
|
P2: type of CHV to unblock
|
||||||
|
@ -682,25 +672,45 @@ class ISO7816(object):
|
||||||
Data: if Lc=0x10, UNBLOCK_CHV (PUK) value and new CHV (PIN) values,
|
Data: if Lc=0x10, UNBLOCK_CHV (PUK) value and new CHV (PIN) values,
|
||||||
each are 8 digits
|
each are 8 digits
|
||||||
call sr_apdu method
|
call sr_apdu method
|
||||||
'''
|
"""
|
||||||
if len(Data) != 16:
|
if len(Data) != 16:
|
||||||
UNBLOCK_CHV = [self.CLA, 0x2C, 0x00, P2]
|
UNBLOCK_CHV = [self.CLA, 0x2C, 0x00, P2]
|
||||||
else:
|
else:
|
||||||
UNBLOCK_CHV = [self.CLA, 0x2C, 0x00, P2, 0x10] + Data
|
UNBLOCK_CHV = [self.CLA, 0x2C, 0x00, P2, 0x10] + Data
|
||||||
return self.sr_apdu(UNBLOCK_CHV)
|
return self.sr_apdu(UNBLOCK_CHV)
|
||||||
|
|
||||||
|
def FETCH(self, Le=0x01):
|
||||||
|
"""
|
||||||
|
APDU command to receive an ICC proactive command
|
||||||
|
that will need to be responded by a TERMINAL RESPONSE
|
||||||
|
|
||||||
|
Le: expected length of data
|
||||||
|
call sr_apdu method
|
||||||
|
"""
|
||||||
|
FETCH = [self.CLA, 0x12, 0x00, 0x00, Le]
|
||||||
|
return self.sr_apdu(FETCH)
|
||||||
|
|
||||||
|
def TERMINAL_RESPONSE(self, Data=[]):
|
||||||
|
"""
|
||||||
|
APDU command to provide a response to an ICC proactive command
|
||||||
|
|
||||||
|
Data: list of bytes for the response to be provided to the ICC
|
||||||
|
"""
|
||||||
|
TERMINAL_RESP = [self.CLA, 0x14, 0x00, 0x00, len(Data)] + Data
|
||||||
|
return self.sr_apdu(TERMINAL_RESP)
|
||||||
|
|
||||||
##########################
|
##########################
|
||||||
# evolved "macro" method for ISO7816 card
|
# evolved "macro" method for ISO7816 card
|
||||||
# need the "coms" attribute being an apdu_stack()
|
# need the "coms" attribute being an apdu_stack()
|
||||||
##########################
|
##########################
|
||||||
def parse_file(self, Data=[]):
|
def parse_file(self, Data=[]):
|
||||||
'''
|
"""
|
||||||
parse_file(self, Data) -> Dict()
|
parse_file(self, Data) -> Dict()
|
||||||
|
|
||||||
parses a list of bytes returned when selecting a file
|
parses a list of bytes returned when selecting a file
|
||||||
interprets the content of some informative bytes
|
interprets the content of some informative bytes
|
||||||
for file structure and parsing method...
|
for file structure and parsing method...
|
||||||
'''
|
"""
|
||||||
ber = BERTLV_parser( Data )
|
ber = BERTLV_parser( Data )
|
||||||
if self.dbg >= 3:
|
if self.dbg >= 3:
|
||||||
log(3, 'BER structure:\n%s' % ber)
|
log(3, 'BER structure:\n%s' % ber)
|
||||||
|
@ -744,13 +754,13 @@ class ISO7816(object):
|
||||||
return fil
|
return fil
|
||||||
|
|
||||||
def parse_FCP(self, Data=[]):
|
def parse_FCP(self, Data=[]):
|
||||||
'''
|
"""
|
||||||
parse_FCP(Data) -> Dict()
|
parse_FCP(Data) -> Dict()
|
||||||
|
|
||||||
parses a list of bytes returned when selecting a file
|
parses a list of bytes returned when selecting a file
|
||||||
interprets the content of some informative bytes
|
interprets the content of some informative bytes
|
||||||
for file structure and parsing method...
|
for file structure and parsing method...
|
||||||
'''
|
"""
|
||||||
fil = {}
|
fil = {}
|
||||||
# loop on the Data bytes to parse TLV'style attributes
|
# loop on the Data bytes to parse TLV'style attributes
|
||||||
toProcess = Data
|
toProcess = Data
|
||||||
|
@ -817,11 +827,11 @@ class ISO7816(object):
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def parse_life_cycle(Data, fil):
|
def parse_life_cycle(Data, fil):
|
||||||
'''
|
"""
|
||||||
parses a list of bytes provided in Data
|
parses a list of bytes provided in Data
|
||||||
interprets the content as the life cycle
|
interprets the content as the life cycle
|
||||||
and enriches the file dictionnary passed as argument
|
and enriches the file dictionnary passed as argument
|
||||||
'''
|
"""
|
||||||
if Data[0] == 1: fil['Life Cycle Status'] = 'creation state'
|
if Data[0] == 1: fil['Life Cycle Status'] = 'creation state'
|
||||||
elif Data[0] == 3: fil['Life Cycle Status'] = 'initialization state'
|
elif Data[0] == 3: fil['Life Cycle Status'] = 'initialization state'
|
||||||
elif Data[0] in (5, 7): fil['Life Cycle Status'] = 'operational state' \
|
elif Data[0] in (5, 7): fil['Life Cycle Status'] = 'operational state' \
|
||||||
|
@ -836,11 +846,11 @@ class ISO7816(object):
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def parse_file_descriptor(Data, fil):
|
def parse_file_descriptor(Data, fil):
|
||||||
'''
|
"""
|
||||||
parses a list of bytes provided in Data
|
parses a list of bytes provided in Data
|
||||||
interprets the content as the file descriptor
|
interprets the content as the file descriptor
|
||||||
and enriches the file dictionnary passed as argument
|
and enriches the file dictionnary passed as argument
|
||||||
'''
|
"""
|
||||||
# parse the File Descriptor Byte
|
# parse the File Descriptor Byte
|
||||||
fd = Data[0]
|
fd = Data[0]
|
||||||
fd_type = (fd >> 3) & 0b00111
|
fd_type = (fd >> 3) & 0b00111
|
||||||
|
@ -881,11 +891,11 @@ class ISO7816(object):
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def parse_proprietary(Data, fil):
|
def parse_proprietary(Data, fil):
|
||||||
'''
|
"""
|
||||||
parses a list of bytes provided in Data
|
parses a list of bytes provided in Data
|
||||||
interprets the content as the proprietary parameters
|
interprets the content as the proprietary parameters
|
||||||
and enriches the file dictionnary passed as argument
|
and enriches the file dictionnary passed as argument
|
||||||
'''
|
"""
|
||||||
propr_tags = {
|
propr_tags = {
|
||||||
0x80:"UICC characteristics",
|
0x80:"UICC characteristics",
|
||||||
0x81:"Application power consumption",
|
0x81:"Application power consumption",
|
||||||
|
@ -904,13 +914,12 @@ class ISO7816(object):
|
||||||
Data = Data[L+2:]
|
Data = Data[L+2:]
|
||||||
return fil
|
return fil
|
||||||
|
|
||||||
|
|
||||||
def parse_compact_security_attribute(self, Data, fil):
|
def parse_compact_security_attribute(self, Data, fil):
|
||||||
'''
|
"""
|
||||||
parses a list of bytes provided in Data
|
parses a list of bytes provided in Data
|
||||||
interprets the content as the compact form for security parameters
|
interprets the content as the compact form for security parameters
|
||||||
and enriches the file dictionnary passed as argument
|
and enriches the file dictionnary passed as argument
|
||||||
'''
|
"""
|
||||||
# See ISO-IEC 7816-4 section 5.4.3, with compact and expanded format
|
# See ISO-IEC 7816-4 section 5.4.3, with compact and expanded format
|
||||||
AM = Data[0]
|
AM = Data[0]
|
||||||
SC = Data[1:]
|
SC = Data[1:]
|
||||||
|
@ -997,10 +1006,10 @@ class ISO7816(object):
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def parse_expanded_security_attribute(Data, fil):
|
def parse_expanded_security_attribute(Data, fil):
|
||||||
'''
|
"""
|
||||||
check references to EF_ARR file containing access conditions
|
check references to EF_ARR file containing access conditions
|
||||||
see ISO 7816-4
|
see ISO 7816-4
|
||||||
'''
|
"""
|
||||||
# self.ARR = {ARR_id:[ARR_content],...}
|
# self.ARR = {ARR_id:[ARR_content],...}
|
||||||
return fil
|
return fil
|
||||||
file_length = len(Data)
|
file_length = len(Data)
|
||||||
|
@ -1023,23 +1032,23 @@ class ISO7816(object):
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def parse_security_attribute(Data, fil):
|
def parse_security_attribute(Data, fil):
|
||||||
'''
|
"""
|
||||||
TODO: to implement...
|
TODO: to implement...
|
||||||
|
|
||||||
need to work further on how to do it (with ref to EF_ARR)
|
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
|
# See ISO-IEC 7816-4 section 5.4.3, with compact and expanded format
|
||||||
# not implemented yet (looks like useless for (U)SIM card ?)
|
# not implemented yet (looks like useless for (U)SIM card ?)
|
||||||
return fil
|
return fil
|
||||||
|
|
||||||
def parse_FCI(self, Data=[]):
|
def parse_FCI(self, Data=[]):
|
||||||
'''
|
"""
|
||||||
parse_FCI(Data) -> Dict()
|
parse_FCI(Data) -> Dict()
|
||||||
|
|
||||||
parses a list of bytes returned when selecting a file
|
parses a list of bytes returned when selecting a file
|
||||||
interprets the content of some informative bytes
|
interprets the content of some informative bytes
|
||||||
for file structure and parsing method...
|
for file structure and parsing method...
|
||||||
'''
|
"""
|
||||||
fil = {}
|
fil = {}
|
||||||
# loop on the Data bytes to parse TLV'style attributes
|
# loop on the Data bytes to parse TLV'style attributes
|
||||||
toProcess = Data
|
toProcess = Data
|
||||||
|
@ -1112,13 +1121,13 @@ class ISO7816(object):
|
||||||
|
|
||||||
|
|
||||||
def read_EF(self, fil):
|
def read_EF(self, fil):
|
||||||
'''
|
"""
|
||||||
interprets the content of file parameters (Structure, Size, Length...)
|
interprets the content of file parameters (Structure, Size, Length...)
|
||||||
and enriches the file dictionnary passed as argument
|
and enriches the file dictionnary passed as argument
|
||||||
with "Data" key and corresponding
|
with "Data" key and corresponding
|
||||||
- list of bytes for EF transparent
|
- list of bytes for EF transparent
|
||||||
- list of list of bytes for cyclic or linear EF
|
- list of list of bytes for cyclic or linear EF
|
||||||
'''
|
"""
|
||||||
# read EF transparent data
|
# read EF transparent data
|
||||||
if fil['Structure'] == 'transparent':
|
if fil['Structure'] == 'transparent':
|
||||||
self.coms.push( self.READ_BINARY(Le=fil['Size']) )
|
self.coms.push( self.READ_BINARY(Le=fil['Size']) )
|
||||||
|
@ -1133,7 +1142,7 @@ class ISO7816(object):
|
||||||
fil['Data'] = []
|
fil['Data'] = []
|
||||||
# for record data: need to check the number of recordings
|
# for record data: need to check the number of recordings
|
||||||
# stored in the file, and iterate for each
|
# stored in the file, and iterate for each
|
||||||
for i in range( (fil['Size'] / fil['Record Length']) ):
|
for i in range( (fil['Size'] // fil['Record Length']) ):
|
||||||
self.coms.push( self.READ_RECORD(P1=i+1, P2=0x04, \
|
self.coms.push( self.READ_RECORD(P1=i+1, P2=0x04, \
|
||||||
Le=fil['Record Length']) )
|
Le=fil['Record Length']) )
|
||||||
if self.coms()[2] != (0x90, 0x00):
|
if self.coms()[2] != (0x90, 0x00):
|
||||||
|
@ -1154,7 +1163,7 @@ class ISO7816(object):
|
||||||
return fil
|
return fil
|
||||||
|
|
||||||
def select(self, addr=[0x3F, 0x00], type="fid", with_length=True):
|
def select(self, addr=[0x3F, 0x00], type="fid", with_length=True):
|
||||||
'''
|
"""
|
||||||
self.select(addr=[0x.., 0x..], type="fid", with_length=True)
|
self.select(addr=[0x.., 0x..], type="fid", with_length=True)
|
||||||
-> dict() on success, None on error
|
-> dict() on success, None on error
|
||||||
|
|
||||||
|
@ -1181,7 +1190,7 @@ class ISO7816(object):
|
||||||
in the SELECT_FILE APDU
|
in the SELECT_FILE APDU
|
||||||
|
|
||||||
APDUs exchanged available thanks to the attribute `self`.coms
|
APDUs exchanged available thanks to the attribute `self`.coms
|
||||||
'''
|
"""
|
||||||
# get the UICC trigger
|
# get the UICC trigger
|
||||||
is_UICC = isinstance(self, UICC)
|
is_UICC = isinstance(self, UICC)
|
||||||
|
|
||||||
|
@ -1228,7 +1237,7 @@ class ISO7816(object):
|
||||||
# take the parse_file() method from the instance:
|
# take the parse_file() method from the instance:
|
||||||
# ISO7816, UICC (for USIM) or SIM
|
# ISO7816, UICC (for USIM) or SIM
|
||||||
file = self.parse_file(data)
|
file = self.parse_file(data)
|
||||||
if file['Type'][0:2] == 'EF':
|
if 'Type' in file.keys() and file['Type'][0:2] == 'EF':
|
||||||
file = self.read_EF(file)
|
file = self.read_EF(file)
|
||||||
|
|
||||||
# finally returns the whole file dictionnary,
|
# finally returns the whole file dictionnary,
|
||||||
|
@ -1240,14 +1249,14 @@ class ISO7816(object):
|
||||||
###############
|
###############
|
||||||
|
|
||||||
def go_to_path(self, path=[], under_AID=None):
|
def go_to_path(self, path=[], under_AID=None):
|
||||||
'''
|
"""
|
||||||
self.go_to_path(path=[0x.., 0x.., 0x.., 0x.., ..], under_AID=None)
|
self.go_to_path(path=[0x.., 0x.., 0x.., 0x.., ..], under_AID=None)
|
||||||
-> void
|
-> void
|
||||||
|
|
||||||
selects all DF addresses successively from the path given
|
selects all DF addresses successively from the path given
|
||||||
uses the .select() method with "fid" as selection type
|
uses the .select() method with "fid" as selection type
|
||||||
works with AID number too
|
works with AID number too
|
||||||
'''
|
"""
|
||||||
# check path length
|
# check path length
|
||||||
if len(path) % 2:
|
if len(path) % 2:
|
||||||
log(1, '(go_to_path) path length not correct: %s' % path)
|
log(1, '(go_to_path) path length not correct: %s' % path)
|
||||||
|
@ -1273,13 +1282,13 @@ class ISO7816(object):
|
||||||
# this helps to build the blacklist:
|
# this helps to build the blacklist:
|
||||||
|
|
||||||
def make_blacklist(self, DF_path=[], under_AID=None):
|
def make_blacklist(self, DF_path=[], under_AID=None):
|
||||||
'''
|
"""
|
||||||
self.make_blacklist(DF_path=[0x.., 0x.., 0x.., 0x..], under_AID=None)
|
self.make_blacklist(DF_path=[0x.., 0x.., 0x.., 0x..], under_AID=None)
|
||||||
-> list( DFs )
|
-> list( DFs )
|
||||||
|
|
||||||
check dictionnaries describing MF or AID directory structure
|
check dictionnaries describing MF or AID directory structure
|
||||||
and return DF not to select when scanning for file ID under a DF
|
and return DF not to select when scanning for file ID under a DF
|
||||||
'''
|
"""
|
||||||
# IC card Master File, never reselect it...
|
# IC card Master File, never reselect it...
|
||||||
MF = [0x3F, 0x00]
|
MF = [0x3F, 0x00]
|
||||||
# you should also avoid to reselect it
|
# you should also avoid to reselect it
|
||||||
|
@ -1326,7 +1335,7 @@ class ISO7816(object):
|
||||||
|
|
||||||
def scan_DF(self, dir_path=[], under_AID=None, \
|
def scan_DF(self, dir_path=[], under_AID=None, \
|
||||||
hi_addr=(0, 0xff), lo_addr=(0, 0xff)):
|
hi_addr=(0, 0xff), lo_addr=(0, 0xff)):
|
||||||
'''
|
"""
|
||||||
self.scan_DF(dir_path=[0x.., 0x.., 0x.., 0x..], under_AID=None)
|
self.scan_DF(dir_path=[0x.., 0x.., 0x.., 0x..], under_AID=None)
|
||||||
-> list(filesystem), list(child_DF)
|
-> list(filesystem), list(child_DF)
|
||||||
|
|
||||||
|
@ -1335,7 +1344,7 @@ class ISO7816(object):
|
||||||
lo_addr: 8 LSB of the file address to brute force
|
lo_addr: 8 LSB of the file address to brute force
|
||||||
avoid selecting blacklisted files (MF, parent_DF, brother_DF, current_DF)
|
avoid selecting blacklisted files (MF, parent_DF, brother_DF, current_DF)
|
||||||
return list of all found files (EF, DF) and list of child DF
|
return list of all found files (EF, DF) and list of child DF
|
||||||
'''
|
"""
|
||||||
# build blacklist of addresses from the current directory structure
|
# build blacklist of addresses from the current directory structure
|
||||||
# and selected path, in order to select only child file ID:
|
# and selected path, in order to select only child file ID:
|
||||||
BL = self.make_blacklist(dir_path, under_AID)
|
BL = self.make_blacklist(dir_path, under_AID)
|
||||||
|
@ -1387,7 +1396,7 @@ class ISO7816(object):
|
||||||
return FS, child_DF
|
return FS, child_DF
|
||||||
|
|
||||||
def explore_DF(self, DF_path=[], under_AID=None, recursive=True):
|
def explore_DF(self, DF_path=[], under_AID=None, recursive=True):
|
||||||
'''
|
"""
|
||||||
self.explore_DF(dir_path=[0x.., 0x.., 0x.., 0x..], under_AID=None, \
|
self.explore_DF(dir_path=[0x.., 0x.., 0x.., 0x..], under_AID=None, \
|
||||||
recursive=True)
|
recursive=True)
|
||||||
-> None
|
-> None
|
||||||
|
@ -1397,7 +1406,7 @@ class ISO7816(object):
|
||||||
a certain level)
|
a certain level)
|
||||||
fill in self.FS dictionnary with found DF and files
|
fill in self.FS dictionnary with found DF and files
|
||||||
and self._MF_struct or self._AID`num`_struct with directory structure
|
and self._MF_struct or self._AID`num`_struct with directory structure
|
||||||
'''
|
"""
|
||||||
# init by scanning the given DF_path (MF or AID)
|
# init by scanning the given DF_path (MF or AID)
|
||||||
FS, child_DF = self.scan_DF(DF_path, under_AID)
|
FS, child_DF = self.scan_DF(DF_path, under_AID)
|
||||||
# then init or extend self._MF_struct or
|
# then init or extend self._MF_struct or
|
||||||
|
@ -1439,19 +1448,22 @@ class ISO7816(object):
|
||||||
##############################################
|
##############################################
|
||||||
|
|
||||||
class UICC(ISO7816):
|
class UICC(ISO7816):
|
||||||
'''
|
"""
|
||||||
define attributes, methods and facilities for ETSI UICC card
|
define attributes, methods and facilities for ETSI UICC card
|
||||||
check UICC specifications mainly in ETSI TS 102.221
|
check UICC specifications mainly in ETSI TS 102.221
|
||||||
|
|
||||||
inherits (eventually overrides) methods and objects from ISO7816 class
|
inherits (eventually overrides) methods and objects from ISO7816 class
|
||||||
use self.dbg = 1 or more to print live debugging information
|
use self.dbg = 1 or more to print live debugging information
|
||||||
'''
|
"""
|
||||||
AID_RID = {
|
AID_RID = {
|
||||||
(0xA0, 0x00, 0x00, 0x00, 0x09): 'ETSI',
|
(0xA0, 0x00, 0x00, 0x00, 0x09): 'ETSI',
|
||||||
(0xA0, 0x00, 0x00, 0x00, 0x87): '3GPP',
|
(0xA0, 0x00, 0x00, 0x00, 0x87): '3GPP',
|
||||||
(0xA0, 0x00, 0x00, 0x03, 0x43): '3GPP2',
|
(0xA0, 0x00, 0x00, 0x03, 0x43): '3GPP2',
|
||||||
|
(0xA0, 0x00, 0x00, 0x06, 0x45): 'OneM2M',
|
||||||
(0xA0, 0x00, 0x00, 0x04, 0x12): 'OMA',
|
(0xA0, 0x00, 0x00, 0x04, 0x12): 'OMA',
|
||||||
(0xA0, 0x00, 0x00, 0x04, 0x24): 'WiMAX',
|
(0xA0, 0x00, 0x00, 0x04, 0x24): 'WiMAX',
|
||||||
|
(0xA0, 0x00, 0x00, 0x00, 0x03): 'GlobalPlatform',
|
||||||
|
(0xA0, 0x00, 0x00, 0x01, 0x51): 'GlobalPlatform'
|
||||||
}
|
}
|
||||||
AID_ETSI_app_code = {
|
AID_ETSI_app_code = {
|
||||||
(0x00, 0x00): 'Reserved',
|
(0x00, 0x00): 'Reserved',
|
||||||
|
@ -1469,16 +1481,32 @@ class UICC(ISO7816):
|
||||||
(0x10, 0x04): 'ISIM',
|
(0x10, 0x04): 'ISIM',
|
||||||
(0x10, 0x05): 'USIM API for JavaCard',
|
(0x10, 0x05): 'USIM API for JavaCard',
|
||||||
(0x10, 0x06): 'ISIM API for JavaCard',
|
(0x10, 0x06): 'ISIM API for JavaCard',
|
||||||
(0x10, 0x05): 'Contact Manager API for JavaCard',
|
(0x10, 0x07): 'Contact Manager API for JavaCard',
|
||||||
|
(0x10, 0x08): '3GPP USIM-INI',
|
||||||
|
(0x10, 0x09): '3GPP USIM-RN',
|
||||||
|
(0x10, 0x0A): '3GPP HPSIM',
|
||||||
}
|
}
|
||||||
|
# TODO: check USIM specific AID as defined in TS 31.130, annex C
|
||||||
AID_3GPP2_app_code = {
|
AID_3GPP2_app_code = {
|
||||||
(0x10, 0x02): 'CSIM',
|
(0x10, 0x02): 'CSIM',
|
||||||
}
|
}
|
||||||
|
AID_OneM2M_app_code = {
|
||||||
|
(0x10, 0x01): 'oneM2M UICC',
|
||||||
|
(0x10, 0x02): 'oneM2M 1M2MSM',
|
||||||
|
}
|
||||||
AID_country_code = {
|
AID_country_code = {
|
||||||
(0xFF, 0x33): 'France',
|
(0xFF, 0x33): 'France',
|
||||||
(0xFF, 0x44): 'United Kingdom',
|
(0xFF, 0x44): 'United Kingdom',
|
||||||
(0xFF, 0x49): 'Germany',
|
(0xFF, 0x49): 'Germany',
|
||||||
}
|
}
|
||||||
|
AID_GP_code = {
|
||||||
|
(0xA0, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00): 'GlobalPlatform card manager (before v211)',
|
||||||
|
(0xA0, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00): 'GlobalPlatform card manager (before v211)',
|
||||||
|
(0xA0, 0x00, 0x00, 0x01, 0x51, 0x00, 0x00): 'GlobalPlatform card manager (v211 and after)',
|
||||||
|
(0xA0, 0x00, 0x00, 0x00, 0x18, 0x43, 0x4D, 0x00): 'GlobalPlatform card manager (GemXpresso Pro)'
|
||||||
|
}
|
||||||
|
# TODO: check UICC access control AID as defined in the Android API
|
||||||
|
#https://source.android.com/devices/tech/config/uicc
|
||||||
|
|
||||||
pin_status = {
|
pin_status = {
|
||||||
0x01 : "PIN Appl 1",
|
0x01 : "PIN Appl 1",
|
||||||
|
@ -1535,19 +1563,20 @@ class UICC(ISO7816):
|
||||||
]
|
]
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
'''
|
"""
|
||||||
initializes like an ISO7816-4 card with CLA=0x00
|
initializes like an ISO7816-4 card with CLA=0x00
|
||||||
initialized on the MF
|
initialized on the MF
|
||||||
'''
|
"""
|
||||||
ISO7816.__init__(self, CLA=0x00)
|
ISO7816.__init__(self, CLA=0x00)
|
||||||
self.AID = []
|
self.AID = []
|
||||||
|
self.AID_GP = {}
|
||||||
|
|
||||||
if self.dbg >= 2:
|
if self.dbg >= 2:
|
||||||
log(3, '(UICC.__init__) type definition: %s' % type(self))
|
log(3, '(UICC.__init__) type definition: %s' % type(self))
|
||||||
log(3, '(UICC.__init__) CLA definition: %s' % hex(self.CLA))
|
log(3, '(UICC.__init__) CLA definition: %s' % hex(self.CLA))
|
||||||
|
|
||||||
def parse_file(self, Data=[]):
|
def parse_file(self, Data=[]):
|
||||||
'''
|
"""
|
||||||
parse_file(Data=[0x12, 0x34, 0x56, 0x89]) -> dict(file)
|
parse_file(Data=[0x12, 0x34, 0x56, 0x89]) -> dict(file)
|
||||||
mainly based on the ISO7816 parsing style
|
mainly based on the ISO7816 parsing style
|
||||||
|
|
||||||
|
@ -1555,7 +1584,7 @@ class UICC(ISO7816):
|
||||||
interprets the content of some informative bytes for right accesses,
|
interprets the content of some informative bytes for right accesses,
|
||||||
type / format of file... see TS 102.221
|
type / format of file... see TS 102.221
|
||||||
works over the UICC file structure (quite different from e.g. SIM card)
|
works over the UICC file structure (quite different from e.g. SIM card)
|
||||||
'''
|
"""
|
||||||
# First ISO7816 parsing
|
# First ISO7816 parsing
|
||||||
fil = ISO7816.parse_file(self, Data)
|
fil = ISO7816.parse_file(self, Data)
|
||||||
|
|
||||||
|
@ -1574,11 +1603,11 @@ class UICC(ISO7816):
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def parse_pin_status(Data, fil):
|
def parse_pin_status(Data, fil):
|
||||||
'''
|
"""
|
||||||
parses a list of bytes provided in Data
|
parses a list of bytes provided in Data
|
||||||
interprets the content as the UICC pin status
|
interprets the content as the UICC pin status
|
||||||
and enriches the file dictionnary passed as argument
|
and enriches the file dictionnary passed as argument
|
||||||
'''
|
"""
|
||||||
PS_DO = Data[2:2+Data[1]]
|
PS_DO = Data[2:2+Data[1]]
|
||||||
Data = Data[2+len(PS_DO):]
|
Data = Data[2+len(PS_DO):]
|
||||||
PIN_status = ''
|
PIN_status = ''
|
||||||
|
@ -1620,13 +1649,12 @@ class UICC(ISO7816):
|
||||||
return fil
|
return fil
|
||||||
|
|
||||||
def get_AID(self):
|
def get_AID(self):
|
||||||
'''
|
"""
|
||||||
checks EF_DIR at the MF level,
|
checks EF_DIR at the MF level,
|
||||||
and available AID (Application ID) referenced
|
and available AID (Application ID) referenced
|
||||||
|
|
||||||
puts it into self.AID
|
puts it into self.AID
|
||||||
interprets and print the content of the self.AID list
|
"""
|
||||||
'''
|
|
||||||
#go back to MF and select EF_DIR
|
#go back to MF and select EF_DIR
|
||||||
#self.select(addr=[])
|
#self.select(addr=[])
|
||||||
|
|
||||||
|
@ -1649,9 +1677,9 @@ class UICC(ISO7816):
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def interpret_AID(aid=[]):
|
def interpret_AID(aid=[]):
|
||||||
'''
|
"""
|
||||||
interprets and prints the aid provided
|
returns a string with the interpretation of the AID provided
|
||||||
'''
|
"""
|
||||||
if len(aid) < 11:
|
if len(aid) < 11:
|
||||||
return
|
return
|
||||||
# check AID format
|
# check AID format
|
||||||
|
@ -1670,6 +1698,9 @@ class UICC(ISO7816):
|
||||||
if aid_rid == (0xA0, 0x00, 0x00, 0x03, 0x43) \
|
if aid_rid == (0xA0, 0x00, 0x00, 0x03, 0x43) \
|
||||||
and aid_app in UICC.AID_3GPP2_app_code.keys():
|
and aid_app in UICC.AID_3GPP2_app_code.keys():
|
||||||
aid_app = UICC.AID_3GPP2_app_code[aid_app]
|
aid_app = UICC.AID_3GPP2_app_code[aid_app]
|
||||||
|
if aid_rid == (0xA0, 0x00, 0x00, 0x06, 0x45) \
|
||||||
|
and aid_app in UICC.AID_OneM2M_app_code.keys():
|
||||||
|
aid_app = UICC.AID_OneM2M_app_code[aid_app]
|
||||||
# get AID responsible SDO and country
|
# get AID responsible SDO and country
|
||||||
if aid_rid in UICC.AID_RID.keys():
|
if aid_rid in UICC.AID_RID.keys():
|
||||||
aid_rid = UICC.AID_RID[aid_rid]
|
aid_rid = UICC.AID_RID[aid_rid]
|
||||||
|
@ -1679,11 +1710,25 @@ class UICC(ISO7816):
|
||||||
return('%s || %s || %s || %s || %s' \
|
return('%s || %s || %s || %s || %s' \
|
||||||
% (aid_rid, aid_app, aid_country, aid_provider, tuple(aid[11:])))
|
% (aid_rid, aid_app, aid_country, aid_provider, tuple(aid[11:])))
|
||||||
|
|
||||||
|
def get_AID_GP(self):
|
||||||
|
"""
|
||||||
|
tries to select all AID addresses from AID_GP_app_code at the MF level
|
||||||
|
|
||||||
|
puts those to which there is a positive SW response into self.AID_GP
|
||||||
|
"""
|
||||||
|
for aid in self.AID_GP_code.keys():
|
||||||
|
aid = list(aid)
|
||||||
|
self.select_by_name(aid)
|
||||||
|
if self.coms()[2] == (0x90, 0x00):
|
||||||
|
# positive response, where we could read the data returned by
|
||||||
|
# the application
|
||||||
|
self.AID_GP[tuple(aid)] = BERTLV_extract(self.coms()[3])
|
||||||
|
|
||||||
def get_ICCID(self):
|
def get_ICCID(self):
|
||||||
'''
|
"""
|
||||||
check EF_ICCID at the MF level,
|
check EF_ICCID at the MF level,
|
||||||
and returnq the ASCII value of the ICCID
|
and returnq the ASCII value of the ICCID
|
||||||
'''
|
"""
|
||||||
#go back to MF and select EF_ICCID
|
#go back to MF and select EF_ICCID
|
||||||
#self.select(addr=[])
|
#self.select(addr=[])
|
||||||
|
|
||||||
|
@ -1696,15 +1741,15 @@ class UICC(ISO7816):
|
||||||
return decode_BCD( EF_ICCID['Data'] )
|
return decode_BCD( EF_ICCID['Data'] )
|
||||||
|
|
||||||
def select_by_name(self, name=[]):
|
def select_by_name(self, name=[]):
|
||||||
'''
|
"""
|
||||||
AID selection by name: should be AID bytes
|
AID selection by name: should be AID bytes
|
||||||
'''
|
"""
|
||||||
return self.select(name, 'aid')
|
return self.select(name, 'aid')
|
||||||
|
|
||||||
def select_by_aid(self, aid_num=1):
|
def select_by_aid(self, aid_num=1):
|
||||||
'''
|
"""
|
||||||
AID selection by index
|
AID selection by index
|
||||||
'''
|
"""
|
||||||
if hasattr(self, 'AID') and aid_num <= len(self.AID)+1:
|
if hasattr(self, 'AID') and aid_num <= len(self.AID)+1:
|
||||||
return self.select(self.AID[aid_num-1], 'aid')
|
return self.select(self.AID[aid_num-1], 'aid')
|
||||||
|
|
||||||
|
|
65
card/SIM.py
65
card/SIM.py
|
@ -90,21 +90,22 @@ SIM_service_table = {
|
||||||
56 : "Service Provider Display Information",
|
56 : "Service Provider Display Information",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class SIM(ISO7816):
|
class SIM(ISO7816):
|
||||||
'''
|
"""
|
||||||
define attributes, methods and facilities for ETSI / 3GPP SIM card
|
define attributes, methods and facilities for ETSI / 3GPP SIM card
|
||||||
check SIM specifications in ETSI TS 102.221 and 3GPP TS 51.011
|
check SIM specifications in ETSI TS 102.221 and 3GPP TS 51.011
|
||||||
|
|
||||||
inherit methods and objects from ISO7816 class
|
inherit methods and objects from ISO7816 class
|
||||||
use self.dbg = 1 or more to print live debugging information
|
use self.dbg = 1 or more to print live debugging information
|
||||||
'''
|
"""
|
||||||
|
|
||||||
def __init__(self, atr = None):
|
def __init__(self):
|
||||||
'''
|
"""
|
||||||
initialize like an ISO7816-4 card with CLA=0xA0
|
initialize like an ISO7816-4 card with CLA=0xA0
|
||||||
can also be used for USIM working in SIM mode,
|
can also be used for USIM working in SIM mode,
|
||||||
'''
|
"""
|
||||||
ISO7816.__init__(self, atr, CLA=0xA0)
|
ISO7816.__init__(self, CLA=0xA0)
|
||||||
|
|
||||||
if self.dbg >= 2:
|
if self.dbg >= 2:
|
||||||
log(3, '(SIM.__init__) type definition: %s' % type(self))
|
log(3, '(SIM.__init__) type definition: %s' % type(self))
|
||||||
|
@ -112,13 +113,13 @@ class SIM(ISO7816):
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def sw_status(sw1, sw2):
|
def sw_status(sw1, sw2):
|
||||||
'''
|
"""
|
||||||
sw_status(sw1=int, sw2=int) -> string
|
sw_status(sw1=int, sw2=int) -> string
|
||||||
|
|
||||||
extends SW status bytes interpretation from ISO7816
|
extends SW status bytes interpretation from ISO7816
|
||||||
with ETSI / 3GPP SW codes
|
with ETSI / 3GPP SW codes
|
||||||
helps to speak with the smartcard!
|
helps to speak with the smartcard!
|
||||||
'''
|
"""
|
||||||
status = ISO7816.sw_status(sw1, sw2)
|
status = ISO7816.sw_status(sw1, sw2)
|
||||||
if sw1 == 0x91: status = 'normal processing, with extra info ' \
|
if sw1 == 0x91: status = 'normal processing, with extra info ' \
|
||||||
'containing a command for the terminal: length of the ' \
|
'containing a command for the terminal: length of the ' \
|
||||||
|
@ -158,10 +159,10 @@ class SIM(ISO7816):
|
||||||
return status
|
return status
|
||||||
|
|
||||||
def verify_pin(self, pin='', pin_type=1):
|
def verify_pin(self, pin='', pin_type=1):
|
||||||
'''
|
"""
|
||||||
verify CHV1 (PIN code) or CHV2 with VERIFY APDU command
|
verify CHV1 (PIN code) or CHV2 with VERIFY APDU command
|
||||||
call ISO7816 VERIFY method
|
call ISO7816 VERIFY method
|
||||||
'''
|
"""
|
||||||
if pin_type in [1, 2] and type(pin) is str and \
|
if pin_type in [1, 2] and type(pin) is str and \
|
||||||
len(pin) == 4 and 0 <= int(pin) < 10000:
|
len(pin) == 4 and 0 <= int(pin) < 10000:
|
||||||
PIN = [ord(i) for i in pin] + [0xFF, 0xFF, 0xFF, 0xFF]
|
PIN = [ord(i) for i in pin] + [0xFF, 0xFF, 0xFF, 0xFF]
|
||||||
|
@ -171,12 +172,12 @@ class SIM(ISO7816):
|
||||||
log(2, '(verify_pin) bad input parameters')
|
log(2, '(verify_pin) bad input parameters')
|
||||||
|
|
||||||
def disable_pin(self, pin='', pin_type=1):
|
def disable_pin(self, pin='', pin_type=1):
|
||||||
'''
|
"""
|
||||||
disable CHV1 (PIN code) or CHV2 with DISABLE_CHV APDU command
|
disable CHV1 (PIN code) or CHV2 with DISABLE_CHV APDU command
|
||||||
TIP: do it as soon as you can when you are working
|
TIP: do it as soon as you can when you are working
|
||||||
with a SIM / USIM card for which you know the PIN!
|
with a SIM / USIM card for which you know the PIN!
|
||||||
call ISO7816 DISABLE method
|
call ISO7816 DISABLE method
|
||||||
'''
|
"""
|
||||||
if pin_type in [1, 2] and type(pin) is str and \
|
if pin_type in [1, 2] and type(pin) is str and \
|
||||||
len(pin) == 4 and 0 <= int(pin) < 10000:
|
len(pin) == 4 and 0 <= int(pin) < 10000:
|
||||||
PIN = [ord(i) for i in pin] + [0xFF, 0xFF, 0xFF, 0xFF]
|
PIN = [ord(i) for i in pin] + [0xFF, 0xFF, 0xFF, 0xFF]
|
||||||
|
@ -186,10 +187,10 @@ class SIM(ISO7816):
|
||||||
log(2, '(disable_pin) bad input parameters')
|
log(2, '(disable_pin) bad input parameters')
|
||||||
|
|
||||||
def enable_pin(self, pin='', pin_type=1):
|
def enable_pin(self, pin='', pin_type=1):
|
||||||
'''
|
"""
|
||||||
enable CHV1 (PIN code) or CHV2 with ENABLE_CHV APDU command
|
enable CHV1 (PIN code) or CHV2 with ENABLE_CHV APDU command
|
||||||
call ISO7816 ENABLE method
|
call ISO7816 ENABLE method
|
||||||
'''
|
"""
|
||||||
if pin_type in [1, 2] and type(pin) is str and \
|
if pin_type in [1, 2] and type(pin) is str and \
|
||||||
len(pin) == 4 and 0 <= int(pin) < 10000:
|
len(pin) == 4 and 0 <= int(pin) < 10000:
|
||||||
PIN = [ord(i) for i in pin] + [0xFF, 0xFF, 0xFF, 0xFF]
|
PIN = [ord(i) for i in pin] + [0xFF, 0xFF, 0xFF, 0xFF]
|
||||||
|
@ -199,7 +200,7 @@ class SIM(ISO7816):
|
||||||
log(2, '(enable_pin) bad input parameters')
|
log(2, '(enable_pin) bad input parameters')
|
||||||
|
|
||||||
def unblock_pin(self, pin_type=1, unblock_pin=''):
|
def unblock_pin(self, pin_type=1, unblock_pin=''):
|
||||||
'''
|
"""
|
||||||
WARNING: not correctly implemented!!!
|
WARNING: not correctly implemented!!!
|
||||||
and PUK are in general 8 nums...
|
and PUK are in general 8 nums...
|
||||||
TODO: make it correctly!
|
TODO: make it correctly!
|
||||||
|
@ -208,7 +209,7 @@ class SIM(ISO7816):
|
||||||
unblock CHV1 (PIN code) or CHV2 with UNBLOCK_CHV APDU command
|
unblock CHV1 (PIN code) or CHV2 with UNBLOCK_CHV APDU command
|
||||||
and set 0000 value for new PIN
|
and set 0000 value for new PIN
|
||||||
call ISO7816 UNBLOCK_CHV method
|
call ISO7816 UNBLOCK_CHV method
|
||||||
'''
|
"""
|
||||||
log(1, '(unblock_pin) not implemented: aborting')
|
log(1, '(unblock_pin) not implemented: aborting')
|
||||||
return
|
return
|
||||||
#if pin_type == 1:
|
#if pin_type == 1:
|
||||||
|
@ -225,14 +226,14 @@ class SIM(ISO7816):
|
||||||
#return self.UNBLOCK_CHV(P2=pin_type)
|
#return self.UNBLOCK_CHV(P2=pin_type)
|
||||||
|
|
||||||
def parse_file(self, Data=[]):
|
def parse_file(self, Data=[]):
|
||||||
'''
|
"""
|
||||||
parse_file(Data=[0x12, 0x34, 0x56, 0x89]) -> dict(file)
|
parse_file(Data=[0x12, 0x34, 0x56, 0x89]) -> dict(file)
|
||||||
|
|
||||||
parses a list of bytes returned when selecting a file
|
parses a list of bytes returned when selecting a file
|
||||||
interprets the content of some informative bytes for right accesses,
|
interprets the content of some informative bytes for right accesses,
|
||||||
type / format of file... see TS 51.011
|
type / format of file... see TS 51.011
|
||||||
works over the SIM file structure
|
works over the SIM file structure
|
||||||
'''
|
"""
|
||||||
fil = {}
|
fil = {}
|
||||||
fil['Size'] = Data[2]*0x100 + Data[3]
|
fil['Size'] = Data[2]*0x100 + Data[3]
|
||||||
fil['File Identifier'] = Data[4:6]
|
fil['File Identifier'] = Data[4:6]
|
||||||
|
@ -243,16 +244,16 @@ class SIM(ISO7816):
|
||||||
fil['EF_num'] = Data[15]
|
fil['EF_num'] = Data[15]
|
||||||
fil['codes_num'] = Data[16]
|
fil['codes_num'] = Data[16]
|
||||||
fil['CHV1'] = ('not initialized','initialized')\
|
fil['CHV1'] = ('not initialized','initialized')\
|
||||||
[(Data[18] & 0x80) / 0x80]\
|
[Data[18] >> 7]\
|
||||||
+ ': %d attempts remain' % (Data[18] & 0x0F)
|
+ ': %d attempts remain' % (Data[18] & 0x0F)
|
||||||
fil['unblock_CHV1'] = ('not initialized','initialized')\
|
fil['unblock_CHV1'] = ('not initialized','initialized')\
|
||||||
[(Data[19] & 0x80) / 0x80]\
|
[Data[19] >> 7]\
|
||||||
+ ': %d attempts remain' % (Data[19] & 0x0F)
|
+ ': %d attempts remain' % (Data[19] & 0x0F)
|
||||||
fil['CHV2'] = ('not initialized','initialized')\
|
fil['CHV2'] = ('not initialized','initialized')\
|
||||||
[(Data[20] & 0x80) / 0x80]\
|
[Data[20] >> 7]\
|
||||||
+ ': %d attempts remain' % (Data[20] & 0x0F)
|
+ ': %d attempts remain' % (Data[20] & 0x0F)
|
||||||
fil['unblock_CHV2'] = ('not initialized','initialized')\
|
fil['unblock_CHV2'] = ('not initialized','initialized')\
|
||||||
[(Data[21] & 0x80) / 0x80]\
|
[Data[21] >> 7]\
|
||||||
+ ': %d attempts remain' % (Data[21] & 0x0F)
|
+ ': %d attempts remain' % (Data[21] & 0x0F)
|
||||||
if len(Data) > 23:
|
if len(Data) > 23:
|
||||||
fil['Adm'] = Data[23:]
|
fil['Adm'] = Data[23:]
|
||||||
|
@ -279,7 +280,7 @@ class SIM(ISO7816):
|
||||||
return fil
|
return fil
|
||||||
|
|
||||||
def run_gsm_alg(self, RAND=16*[0x00]):
|
def run_gsm_alg(self, RAND=16*[0x00]):
|
||||||
'''
|
"""
|
||||||
self.run_gsm_alg( RAND ) -> ( SRES, Kc )
|
self.run_gsm_alg( RAND ) -> ( SRES, Kc )
|
||||||
RAND : list of bytes, length 16
|
RAND : list of bytes, length 16
|
||||||
SRES : list of bytes, length 4
|
SRES : list of bytes, length 4
|
||||||
|
@ -289,7 +290,7 @@ class SIM(ISO7816):
|
||||||
accepts any kind of RAND (old GSM fashion)
|
accepts any kind of RAND (old GSM fashion)
|
||||||
feed with RAND 16 bytes value
|
feed with RAND 16 bytes value
|
||||||
returns a list with SRES and Kc, or None on error
|
returns a list with SRES and Kc, or None on error
|
||||||
'''
|
"""
|
||||||
if len(RAND) != 16:
|
if len(RAND) != 16:
|
||||||
if self.dbg:
|
if self.dbg:
|
||||||
log(1, '(run_gsm_alg) bad RAND value: aborting')
|
log(1, '(run_gsm_alg) bad RAND value: aborting')
|
||||||
|
@ -316,12 +317,12 @@ class SIM(ISO7816):
|
||||||
return [ SRES, Kc ]
|
return [ SRES, Kc ]
|
||||||
|
|
||||||
def get_imsi(self):
|
def get_imsi(self):
|
||||||
'''
|
"""
|
||||||
self.get_imsi() -> string(IMSI)
|
self.get_imsi() -> string(IMSI)
|
||||||
|
|
||||||
reads IMSI value at address [0x6F, 0x07]
|
reads IMSI value at address [0x6F, 0x07]
|
||||||
returns IMSI string on success or None on error
|
returns IMSI string on success or None on error
|
||||||
'''
|
"""
|
||||||
# select DF_GSM for SIM card
|
# select DF_GSM for SIM card
|
||||||
self.select([0x7F, 0x20])
|
self.select([0x7F, 0x20])
|
||||||
if self.coms()[2] != (0x90, 0x00):
|
if self.coms()[2] != (0x90, 0x00):
|
||||||
|
@ -346,12 +347,12 @@ class SIM(ISO7816):
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def get_services(self):
|
def get_services(self):
|
||||||
'''
|
"""
|
||||||
self.get_services() -> None
|
self.get_services() -> None
|
||||||
|
|
||||||
reads SIM Service Table at address [0x6F, 0x38]
|
reads SIM Service Table at address [0x6F, 0x38]
|
||||||
returns list of services allowed / activated
|
returns list of services allowed / activated
|
||||||
'''
|
"""
|
||||||
# select DF_GSM for SIM card
|
# select DF_GSM for SIM card
|
||||||
self.select([0x7F, 0x20])
|
self.select([0x7F, 0x20])
|
||||||
if self.coms()[2] != (0x90, 0x00):
|
if self.coms()[2] != (0x90, 0x00):
|
||||||
|
@ -371,13 +372,13 @@ class SIM(ISO7816):
|
||||||
return self.get_services_from_sst(sst['Data'])
|
return self.get_services_from_sst(sst['Data'])
|
||||||
|
|
||||||
def read_services(self):
|
def read_services(self):
|
||||||
'''
|
"""
|
||||||
self.read_services() -> None
|
self.read_services() -> None
|
||||||
|
|
||||||
reads SIM Service Table at address [0x6F, 0x38]
|
reads SIM Service Table at address [0x6F, 0x38]
|
||||||
prints services allowed / activated
|
prints services allowed / activated
|
||||||
returns None
|
returns None
|
||||||
'''
|
"""
|
||||||
serv = self.get_services()
|
serv = self.get_services()
|
||||||
for s in serv:
|
for s in serv:
|
||||||
print(s)
|
print(s)
|
||||||
|
@ -401,7 +402,7 @@ class SIM(ISO7816):
|
||||||
return services
|
return services
|
||||||
|
|
||||||
def explore_fs(self, filename='sim_fs', depth=True, emul=False):
|
def explore_fs(self, filename='sim_fs', depth=True, emul=False):
|
||||||
'''
|
"""
|
||||||
self.explore_fs(self, filename='sim_fs') -> void
|
self.explore_fs(self, filename='sim_fs') -> void
|
||||||
filename: file to write in information found
|
filename: file to write in information found
|
||||||
depth: depth in recursivity, True=infinite
|
depth: depth in recursivity, True=infinite
|
||||||
|
@ -409,7 +410,7 @@ class SIM(ISO7816):
|
||||||
brute force all file addresses from MF recursively
|
brute force all file addresses from MF recursively
|
||||||
(until no more DF are found)
|
(until no more DF are found)
|
||||||
write information on existing DF and file in the output file
|
write information on existing DF and file in the output file
|
||||||
'''
|
"""
|
||||||
simfs_entries = MF_FS.keys()
|
simfs_entries = MF_FS.keys()
|
||||||
if not emul:
|
if not emul:
|
||||||
self.explore_DF([], None, depth)
|
self.explore_DF([], None, depth)
|
||||||
|
|
86
card/USIM.py
86
card/USIM.py
|
@ -132,26 +132,60 @@ USIM_service_table = {
|
||||||
95 : 'Support of UICC access to IMS',
|
95 : 'Support of UICC access to IMS',
|
||||||
96 : 'Non-Access Stratum configuration by USIM',
|
96 : 'Non-Access Stratum configuration by USIM',
|
||||||
97 : 'PWS configuration by USIM',
|
97 : 'PWS configuration by USIM',
|
||||||
|
98 : 'RFU',
|
||||||
|
99 : 'URI support by UICC',
|
||||||
|
100: 'Extended EARFCN support',
|
||||||
|
101: 'ProSe',
|
||||||
|
102: 'USAT Application Pairing',
|
||||||
|
103: 'Media Type support',
|
||||||
|
104: 'IMS call disconnection cause',
|
||||||
|
105: 'URI support for MO SHORT MESSAGE CONTROL',
|
||||||
|
106: 'ePDG configuration Information support',
|
||||||
|
107: 'ePDG configuration Information configured',
|
||||||
|
108: 'ACDC support',
|
||||||
|
109: 'Mission Critical Services',
|
||||||
|
110: 'ePDG configuration Information for Emergency Service support',
|
||||||
|
111: 'ePDG configuration Information for Emergency Service configured',
|
||||||
|
112: 'eCall Data over IMS',
|
||||||
|
113: 'URI support for SMS-PP DOWNLOAD as defined in 3GPP TS 31.111',
|
||||||
|
114: 'From Preferred',
|
||||||
|
115: 'IMS configuration data',
|
||||||
|
116: 'TV configuration',
|
||||||
|
117: '3GPP PS Data Off',
|
||||||
|
118: '3GPP PS Data Off Service List',
|
||||||
|
119: 'V2X',
|
||||||
|
120: 'XCAP Configuration Data',
|
||||||
|
121: 'EARFCN list for MTC/NB-IOT UEs',
|
||||||
|
122: '5GS Mobility Management Information',
|
||||||
|
123: '5G Security Parameters',
|
||||||
|
124: 'Subscription identifier privacy support',
|
||||||
|
125: 'SUCI calculation by the USIM',
|
||||||
|
126: 'UAC Access Identities support',
|
||||||
|
127: 'Control plane-based steering of UE in VPLMN',
|
||||||
|
128: 'Call control on PDU Session by USIM',
|
||||||
|
129: '5GS Operator PLMN List',
|
||||||
|
130: 'Support for SUPI of type network specific identifier',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class USIM(UICC):
|
class USIM(UICC):
|
||||||
'''
|
"""
|
||||||
defines attributes, methods and facilities for ETSI / 3GPP USIM card
|
defines attributes, methods and facilities for ETSI / 3GPP USIM card
|
||||||
check USIM specifications in 3GPP TS 31.102
|
check USIM specifications in 3GPP TS 31.102
|
||||||
|
|
||||||
inherits (eventually overrides) methods and objects from UICC class
|
inherits (eventually overrides) methods and objects from UICC class
|
||||||
use self.dbg = 1 or more to print live debugging information
|
use self.dbg = 1 or more to print live debugging information
|
||||||
'''
|
"""
|
||||||
|
|
||||||
def __init__(self, atr = None):
|
def __init__(self):
|
||||||
'''
|
"""
|
||||||
initializes like an ISO7816-4 card with CLA=0x00
|
initializes like an ISO7816-4 card with CLA=0x00
|
||||||
and checks available AID (Application ID) read from EF_DIR
|
and checks available AID (Application ID) read from EF_DIR
|
||||||
|
|
||||||
initializes on the MF
|
initializes on the MF
|
||||||
'''
|
"""
|
||||||
# initialize like a UICC
|
# initialize like a UICC
|
||||||
ISO7816.__init__(self, atr, CLA=0x00)
|
ISO7816.__init__(self, CLA=0x00)
|
||||||
self.AID = []
|
self.AID = []
|
||||||
|
|
||||||
if self.dbg >= 2:
|
if self.dbg >= 2:
|
||||||
|
@ -209,12 +243,12 @@ class USIM(UICC):
|
||||||
return status
|
return status
|
||||||
|
|
||||||
def get_imsi(self):
|
def get_imsi(self):
|
||||||
'''
|
"""
|
||||||
get_imsi() -> string(IMSI)
|
get_imsi() -> string(IMSI)
|
||||||
|
|
||||||
reads IMSI value at address [0x6F, 0x07]
|
reads IMSI value at address [0x6F, 0x07]
|
||||||
returns IMSI string on success or None on error
|
returns IMSI string on success or None on error
|
||||||
'''
|
"""
|
||||||
# select IMSI file
|
# select IMSI file
|
||||||
imsi = self.select([0x6F, 0x07])
|
imsi = self.select([0x6F, 0x07])
|
||||||
if imsi is None:
|
if imsi is None:
|
||||||
|
@ -229,14 +263,14 @@ class USIM(UICC):
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def get_CS_keys(self):
|
def get_CS_keys(self):
|
||||||
'''
|
"""
|
||||||
get_CS_keys() -> [KSI, CK, IK]
|
get_CS_keys() -> [KSI, CK, IK]
|
||||||
|
|
||||||
reads CS UMTS keys at address [0x6F, 0x08]
|
reads CS UMTS keys at address [0x6F, 0x08]
|
||||||
returns list of 3 keys, each are list of bytes, on success
|
returns list of 3 keys, each are list of bytes, on success
|
||||||
(or eventually the whole file dict if the format is strange)
|
(or eventually the whole file dict if the format is strange)
|
||||||
or None on error
|
or None on error
|
||||||
'''
|
"""
|
||||||
EF_KEYS = self.select( [0x6F, 0x08] )
|
EF_KEYS = self.select( [0x6F, 0x08] )
|
||||||
if self.coms()[2] == (0x90, 0x00):
|
if self.coms()[2] == (0x90, 0x00):
|
||||||
if len(EF_KEYS['Data']) == 33:
|
if len(EF_KEYS['Data']) == 33:
|
||||||
|
@ -251,14 +285,14 @@ class USIM(UICC):
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def get_PS_keys(self):
|
def get_PS_keys(self):
|
||||||
'''
|
"""
|
||||||
get_PS_keys() -> [KSI, CK_PS, IK_PS]
|
get_PS_keys() -> [KSI, CK_PS, IK_PS]
|
||||||
|
|
||||||
reads PS UMTS keys at address [0x6F, 0x09]
|
reads PS UMTS keys at address [0x6F, 0x09]
|
||||||
returns list of 3 keys, each are list of bytes, on success
|
returns list of 3 keys, each are list of bytes, on success
|
||||||
(or eventually the whole file dict if the format is strange)
|
(or eventually the whole file dict if the format is strange)
|
||||||
or None on error
|
or None on error
|
||||||
'''
|
"""
|
||||||
EF_KEYSPS = self.select( [0x6F, 0x09] )
|
EF_KEYSPS = self.select( [0x6F, 0x09] )
|
||||||
if self.coms()[2] == (0x90, 0x00):
|
if self.coms()[2] == (0x90, 0x00):
|
||||||
if len(EF_KEYSPS['Data']) == 33:
|
if len(EF_KEYSPS['Data']) == 33:
|
||||||
|
@ -273,7 +307,7 @@ class USIM(UICC):
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def get_GBA_BP(self):
|
def get_GBA_BP(self):
|
||||||
'''
|
"""
|
||||||
get_GBA_BP() -> [[RAND, B-TID, KeyLifetime], ...],
|
get_GBA_BP() -> [[RAND, B-TID, KeyLifetime], ...],
|
||||||
Length-Value parsing style
|
Length-Value parsing style
|
||||||
|
|
||||||
|
@ -282,7 +316,7 @@ class USIM(UICC):
|
||||||
returns list of list of bytes on success
|
returns list of list of bytes on success
|
||||||
(or eventually the whole file dict if the format is strange)
|
(or eventually the whole file dict if the format is strange)
|
||||||
or None on error
|
or None on error
|
||||||
'''
|
"""
|
||||||
EF_GBABP = self.select( [0x6F, 0xD6] )
|
EF_GBABP = self.select( [0x6F, 0xD6] )
|
||||||
if self.coms()[2] == (0x90, 0x00):
|
if self.coms()[2] == (0x90, 0x00):
|
||||||
if len(EF_GBABP['Data']) > 2:
|
if len(EF_GBABP['Data']) > 2:
|
||||||
|
@ -296,7 +330,7 @@ class USIM(UICC):
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def update_GBA_BP(self, RAND, B_TID, key_lifetime):
|
def update_GBA_BP(self, RAND, B_TID, key_lifetime):
|
||||||
'''
|
"""
|
||||||
update_GBA_BP([RAND], [B_TID], [key_lifetime])
|
update_GBA_BP([RAND], [B_TID], [key_lifetime])
|
||||||
-> void (or EF_GBABP file dict if RAND not found)
|
-> void (or EF_GBABP file dict if RAND not found)
|
||||||
|
|
||||||
|
@ -305,7 +339,7 @@ class USIM(UICC):
|
||||||
and updates the file structure with provided B-TID and KeyLifetime
|
and updates the file structure with provided B-TID and KeyLifetime
|
||||||
returns nothing (or eventually the whole file dict
|
returns nothing (or eventually the whole file dict
|
||||||
if the RAND is not found)
|
if the RAND is not found)
|
||||||
'''
|
"""
|
||||||
GBA_BP = self.get_GBA_BP()
|
GBA_BP = self.get_GBA_BP()
|
||||||
for i in GBA_BP:
|
for i in GBA_BP:
|
||||||
if i == RAND:
|
if i == RAND:
|
||||||
|
@ -328,14 +362,14 @@ class USIM(UICC):
|
||||||
return GBA_BP
|
return GBA_BP
|
||||||
|
|
||||||
def get_GBA_NL(self):
|
def get_GBA_NL(self):
|
||||||
'''
|
"""
|
||||||
get_GBA_NL() -> [[NAF_ID, B-TID], ...] , TLV parsing style
|
get_GBA_NL() -> [[NAF_ID, B-TID], ...] , TLV parsing style
|
||||||
|
|
||||||
reads EF_GBANL file at address [0x6F, 0xDA], containing NAF_ID and B-TID
|
reads EF_GBANL file at address [0x6F, 0xDA], containing NAF_ID and B-TID
|
||||||
returns list of list of bytes vector on success
|
returns list of list of bytes vector on success
|
||||||
(or eventually the whole file dict if the format is strange)
|
(or eventually the whole file dict if the format is strange)
|
||||||
or None on error
|
or None on error
|
||||||
'''
|
"""
|
||||||
EF_GBANL = self.select( [0x6F, 0xDA] )
|
EF_GBANL = self.select( [0x6F, 0xDA] )
|
||||||
if self.coms()[2] == (0x90, 0x00):
|
if self.coms()[2] == (0x90, 0x00):
|
||||||
if len(EF_GBANL['Data'][0]) > 2:
|
if len(EF_GBANL['Data'][0]) > 2:
|
||||||
|
@ -366,7 +400,7 @@ class USIM(UICC):
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def authenticate(self, RAND=[], AUTN=[], ctx='3G'):
|
def authenticate(self, RAND=[], AUTN=[], ctx='3G'):
|
||||||
'''
|
"""
|
||||||
self.authenticate(RAND, AUTN, ctx='3G') -> [key1, key2...],
|
self.authenticate(RAND, AUTN, ctx='3G') -> [key1, key2...],
|
||||||
LV parsing style
|
LV parsing style
|
||||||
|
|
||||||
|
@ -380,7 +414,7 @@ class USIM(UICC):
|
||||||
[RES] or [AUTS] for 'GBA'
|
[RES] or [AUTS] for 'GBA'
|
||||||
[RES, Kc] for '2G'
|
[RES, Kc] for '2G'
|
||||||
or None on error
|
or None on error
|
||||||
'''
|
"""
|
||||||
# prepare input data for authentication
|
# prepare input data for authentication
|
||||||
if ctx in ('3G', 'VGCS', 'GBA', 'MBMS') and len(RAND) != 16 \
|
if ctx in ('3G', 'VGCS', 'GBA', 'MBMS') and len(RAND) != 16 \
|
||||||
and len(AUTN) != 16:
|
and len(AUTN) != 16:
|
||||||
|
@ -446,7 +480,7 @@ class USIM(UICC):
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def GBA_derivation(self, NAF_ID=[], IMPI=[]):
|
def GBA_derivation(self, NAF_ID=[], IMPI=[]):
|
||||||
'''
|
"""
|
||||||
self.GBA_derivation(NAF_ID, IMPI) -> [Ks_ext_naf]
|
self.GBA_derivation(NAF_ID, IMPI) -> [Ks_ext_naf]
|
||||||
|
|
||||||
runs the INTERNAL AUTHENTICATE command in the USIM
|
runs the INTERNAL AUTHENTICATE command in the USIM
|
||||||
|
@ -464,7 +498,7 @@ class USIM(UICC):
|
||||||
or None on error
|
or None on error
|
||||||
|
|
||||||
see TS 33.220 for GBA specific formats
|
see TS 33.220 for GBA specific formats
|
||||||
'''
|
"""
|
||||||
# need to run 1st an authenicate command with 'GBA' context,
|
# need to run 1st an authenicate command with 'GBA' context,
|
||||||
# so to have the required keys in the USIM
|
# so to have the required keys in the USIM
|
||||||
P2 = 0x84
|
P2 = 0x84
|
||||||
|
@ -486,13 +520,13 @@ class USIM(UICC):
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def get_services(self):
|
def get_services(self):
|
||||||
'''
|
"""
|
||||||
self.get_services() -> None
|
self.get_services() -> None
|
||||||
|
|
||||||
reads USIM Service Table at address [0x6F, 0x38]
|
reads USIM Service Table at address [0x6F, 0x38]
|
||||||
prints services allowed / activated
|
prints services allowed / activated
|
||||||
returns None
|
returns None
|
||||||
'''
|
"""
|
||||||
# select SST file
|
# select SST file
|
||||||
sst = self.select([0x6F, 0x38])
|
sst = self.select([0x6F, 0x38])
|
||||||
if self.coms()[2] != (0x90, 0x00):
|
if self.coms()[2] != (0x90, 0x00):
|
||||||
|
@ -525,7 +559,7 @@ class USIM(UICC):
|
||||||
return services
|
return services
|
||||||
|
|
||||||
def explore_fs(self, filename='usim_fs', depth=2):
|
def explore_fs(self, filename='usim_fs', depth=2):
|
||||||
'''
|
"""
|
||||||
self.explore_fs(self, filename='usim_fs') -> void
|
self.explore_fs(self, filename='usim_fs') -> void
|
||||||
filename: file to write in information found
|
filename: file to write in information found
|
||||||
depth: depth in recursivity, True=infinite
|
depth: depth in recursivity, True=infinite
|
||||||
|
@ -533,7 +567,7 @@ class USIM(UICC):
|
||||||
brute force all file addresses from 1st USIM AID
|
brute force all file addresses from 1st USIM AID
|
||||||
with a maximum recursion level (to avoid infinite looping...)
|
with a maximum recursion level (to avoid infinite looping...)
|
||||||
write information on existing DF and file in the output file
|
write information on existing DF and file in the output file
|
||||||
'''
|
"""
|
||||||
usimfs_entries = USIM_app_FS.keys()
|
usimfs_entries = USIM_app_FS.keys()
|
||||||
self.explore_DF([], self.AID.index(self.USIM_AID)+1, depth)
|
self.explore_DF([], self.AID.index(self.USIM_AID)+1, depth)
|
||||||
|
|
||||||
|
|
112
card/utils.py
112
card/utils.py
|
@ -23,6 +23,8 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
# being used in smartcard specs #
|
# being used in smartcard specs #
|
||||||
#################################
|
#################################
|
||||||
|
|
||||||
|
import sys
|
||||||
|
|
||||||
from collections import deque
|
from collections import deque
|
||||||
from smartcard.util import toBytes
|
from smartcard.util import toBytes
|
||||||
|
|
||||||
|
@ -36,8 +38,38 @@ def log(level, string):
|
||||||
# but here, just print()
|
# but here, just print()
|
||||||
print('[%s] %s' % (log_levels[level], string))
|
print('[%s] %s' % (log_levels[level], string))
|
||||||
|
|
||||||
|
|
||||||
|
BER_TAG = {
|
||||||
|
1 : 'BOOLEAN',
|
||||||
|
2 : 'INTEGER',
|
||||||
|
3 : 'BIT STRING',
|
||||||
|
4 : 'OCTET STRING',
|
||||||
|
5 : 'NULL',
|
||||||
|
6 : 'OID',
|
||||||
|
7 : 'ObkectDescriptor',
|
||||||
|
8 : 'EXTERNAL',
|
||||||
|
9 : 'REAL',
|
||||||
|
10: 'ENUMERATED',
|
||||||
|
11: 'EMBEDDED-PDV',
|
||||||
|
12: 'UTF8String',
|
||||||
|
13: 'RELATIVE-OID',
|
||||||
|
16: 'SEQUENCE',
|
||||||
|
17: 'SET',
|
||||||
|
19: 'PrintableString',
|
||||||
|
22: 'IA5String',
|
||||||
|
23: 'UTCTime',
|
||||||
|
24: 'GeneralizedTime',
|
||||||
|
26: 'VisibleString',
|
||||||
|
31: 'DATE',
|
||||||
|
32: 'TIME-OF-DAY',
|
||||||
|
33: 'DATE-TIME',
|
||||||
|
34: 'DURATION',
|
||||||
|
35: 'OID-IRI',
|
||||||
|
36: 'RELATIVE-OID-IRI'
|
||||||
|
}
|
||||||
|
|
||||||
# from python 2.6, format('b') allows to use 0b10010110 notation:
|
# from python 2.6, format('b') allows to use 0b10010110 notation:
|
||||||
# much convinient
|
# much convenient
|
||||||
def byteToBit(byte):
|
def byteToBit(byte):
|
||||||
'''
|
'''
|
||||||
byteToBit(0xAB) -> [1, 0, 1, 0, 1, 0, 1, 1]
|
byteToBit(0xAB) -> [1, 0, 1, 0, 1, 0, 1, 1]
|
||||||
|
@ -59,10 +91,16 @@ def stringToByte(string):
|
||||||
|
|
||||||
converts a string into a list of bytes
|
converts a string into a list of bytes
|
||||||
'''
|
'''
|
||||||
|
if sys.version_info[0] < 3:
|
||||||
bytelist = []
|
bytelist = []
|
||||||
for c in string:
|
for c in string:
|
||||||
bytelist.extend( toBytes(c.encode('hex')) )
|
bytelist.extend( toBytes(c.encode('hex')) )
|
||||||
return bytelist
|
return bytelist
|
||||||
|
else:
|
||||||
|
if isinstance(string, str):
|
||||||
|
return list(string.encode('ascii'))
|
||||||
|
else:
|
||||||
|
return list(string)
|
||||||
|
|
||||||
# equivalent to the pyscard function "toASCIIString"
|
# equivalent to the pyscard function "toASCIIString"
|
||||||
def byteToString(bytelist):
|
def byteToString(bytelist):
|
||||||
|
@ -71,10 +109,13 @@ def byteToString(bytelist):
|
||||||
|
|
||||||
converts a list of bytes into a string
|
converts a list of bytes into a string
|
||||||
'''
|
'''
|
||||||
|
if sys.version_info[0] < 3:
|
||||||
string = ''
|
string = ''
|
||||||
for b in bytelist:
|
for b in bytelist:
|
||||||
string += chr(b)
|
string += chr(b)
|
||||||
return string
|
return string
|
||||||
|
else:
|
||||||
|
return bytes(bytelist)
|
||||||
|
|
||||||
def LV_parser(bytelist):
|
def LV_parser(bytelist):
|
||||||
'''
|
'''
|
||||||
|
@ -178,7 +219,8 @@ def first_BERTLV_parser(bytelist):
|
||||||
Len_num = bytelist[i+1] - 0x80
|
Len_num = bytelist[i+1] - 0x80
|
||||||
Len = reduce(lambda x,y: (x<<8)+y, bytelist[i+2:i+2+Len_num])
|
Len = reduce(lambda x,y: (x<<8)+y, bytelist[i+2:i+2+Len_num])
|
||||||
Val = bytelist[i+2+Len_num:i+2+Len_num+Len]
|
Val = bytelist[i+2+Len_num:i+2+Len_num+Len]
|
||||||
# Length coded with 1 byte
|
|
||||||
|
# Length coded with 1 byte (BER short form)
|
||||||
else:
|
else:
|
||||||
Len_num = 1
|
Len_num = 1
|
||||||
Len = bytelist[i+1]
|
Len = bytelist[i+1]
|
||||||
|
@ -204,6 +246,66 @@ def BERTLV_parser(bytelist):
|
||||||
bytelist = bytelist[ T[0] + L[0] + L[1] : ]
|
bytelist = bytelist[ T[0] + L[0] + L[1] : ]
|
||||||
return ret
|
return ret
|
||||||
|
|
||||||
|
def BERTLV_extract(bytelist):
|
||||||
|
'''
|
||||||
|
BERTLV_extract([]) -> {}
|
||||||
|
|
||||||
|
parse the input bytes as BERTLV structure recursively until no more
|
||||||
|
constructed object are present, and returns a corresponding dict of
|
||||||
|
{tag_value: (tag_complete, data_value)}
|
||||||
|
'''
|
||||||
|
ret = []
|
||||||
|
comps = BERTLV_parser(bytelist)
|
||||||
|
for comp in comps:
|
||||||
|
if comp[0][1] == 'primitive':
|
||||||
|
if comp[0][0] == 'universal' and comp[0][2] in BER_TAG:
|
||||||
|
ret.append( [[comp[0][0], comp[0][2], BER_TAG[comp[0][2]]],
|
||||||
|
comp[2]] )
|
||||||
|
#if comp[0][2] == 6:
|
||||||
|
# # decode OID
|
||||||
|
# ret[-1].append( decode_OID(ret[-1][1]) )
|
||||||
|
else:
|
||||||
|
ret.append( [[comp[0][0], comp[0][2]], comp[2]] )
|
||||||
|
else:
|
||||||
|
if comp[0][0] == 'universal' and comp[0][2] in BER_TAG:
|
||||||
|
ret.append( [[comp[0][0], comp[0][2], BER_TAG[comp[0][2]]],
|
||||||
|
BERTLV_extract(comp[2])] )
|
||||||
|
else:
|
||||||
|
ret.append( [[comp[0][0], comp[0][2]],
|
||||||
|
BERTLV_extract(comp[2])] )
|
||||||
|
return ret
|
||||||
|
|
||||||
|
def decode_OID(data=[]):
|
||||||
|
'''
|
||||||
|
decode a BER-encoded ASN.1 OID into a string representing the ASN.1 OID
|
||||||
|
abstract value
|
||||||
|
'''
|
||||||
|
if not data:
|
||||||
|
return ''
|
||||||
|
arcs = []
|
||||||
|
# decode OID arc values
|
||||||
|
v = 0
|
||||||
|
for b in data:
|
||||||
|
v <<= 7
|
||||||
|
if b&0x80:
|
||||||
|
v += (b&0x7f)
|
||||||
|
else:
|
||||||
|
v += b
|
||||||
|
arcs.append(v)
|
||||||
|
v = 0
|
||||||
|
if v != 0:
|
||||||
|
# invalid or incomplete OID
|
||||||
|
return ''
|
||||||
|
#
|
||||||
|
if arcs[0] < 40:
|
||||||
|
return '0 ' + ' '.join(['%i' % v for v in arcs])
|
||||||
|
elif 40 <= arcs[0] < 80:
|
||||||
|
arcs[0] = arcs[0]-40
|
||||||
|
return '1 ' + ' '.join(['%i' % v for v in arcs])
|
||||||
|
else:
|
||||||
|
arcs[0] = arcs[0]-80
|
||||||
|
return '2 ' + ' '.join(['%i' % v for v in data])
|
||||||
|
|
||||||
def decode_BCD(data=[]):
|
def decode_BCD(data=[]):
|
||||||
'''
|
'''
|
||||||
decode_BCD([0x21, 0xFE, 0xA3]) -> '121415310'
|
decode_BCD([0x21, 0xFE, 0xA3]) -> '121415310'
|
||||||
|
@ -216,9 +318,6 @@ def decode_BCD(data=[]):
|
||||||
if (B&0x0F) < 10: string += str(B&0x0F)
|
if (B&0x0F) < 10: string += str(B&0x0F)
|
||||||
# 2nd digit (4 MSB), can be padding (e.g. 0xF)
|
# 2nd digit (4 MSB), can be padding (e.g. 0xF)
|
||||||
if (B>>4) < 10: string += str(B>>4)
|
if (B>>4) < 10: string += str(B>>4)
|
||||||
if len(string) <= 0:
|
|
||||||
return None
|
|
||||||
else:
|
|
||||||
return string
|
return string
|
||||||
|
|
||||||
def compute_luhn(digit_str=''):
|
def compute_luhn(digit_str=''):
|
||||||
|
@ -246,8 +345,7 @@ def write_dict(dict, fd):
|
||||||
'''
|
'''
|
||||||
write a dict() content to a file descriptor
|
write a dict() content to a file descriptor
|
||||||
'''
|
'''
|
||||||
keys = dict.keys()
|
keys = sorted(dict.keys())
|
||||||
keys.sort()
|
|
||||||
fd.write('\n')
|
fd.write('\n')
|
||||||
for k in keys:
|
for k in keys:
|
||||||
rec = dict[k]
|
rec = dict[k]
|
||||||
|
|
|
@ -85,7 +85,7 @@ class Simcard():
|
||||||
# Constructor: Create a new simcard object
|
# Constructor: Create a new simcard object
|
||||||
def __init__(self, cardtype = GSM_USIM, atr = None):
|
def __init__(self, cardtype = GSM_USIM, atr = None):
|
||||||
if cardtype == GSM_USIM:
|
if cardtype == GSM_USIM:
|
||||||
self.card = USIM(atr)
|
self.card = USIM()
|
||||||
self.usim = True
|
self.usim = True
|
||||||
|
|
||||||
# Detect ISIM / USIM applications
|
# Detect ISIM / USIM applications
|
||||||
|
|
Loading…
Reference in New Issue