Browse Source

Sync 'card' library with upstream

This reflects 2a81963790 of
https://github.com/mitshell/card.git
master
Harald Welte 1 year ago
parent
commit
de31d9f88b
  1. 305
      card/ICC.py
  2. 65
      card/SIM.py
  3. 86
      card/USIM.py
  4. 136
      card/utils.py
  5. 2
      simcard.py

305
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

@ -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

@ -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] )