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