|
|
|
# -*- coding: utf-8 -*-
|
|
|
|
|
|
|
|
""" pySim: tell old 2G SIMs apart from UICC
|
|
|
|
"""
|
|
|
|
|
|
|
|
#
|
|
|
|
# (C) 2021 by Sysmocom s.f.m.c. GmbH
|
|
|
|
# All Rights Reserved
|
|
|
|
#
|
|
|
|
# This program is free software: you can redistribute it and/or modify
|
|
|
|
# it under the terms of the GNU General Public License as published by
|
|
|
|
# the Free Software Foundation, either version 2 of the License, or
|
|
|
|
# (at your option) any later version.
|
|
|
|
#
|
|
|
|
# This program is distributed in the hope that it will be useful,
|
|
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
# GNU General Public License for more details.
|
|
|
|
#
|
|
|
|
# You should have received a copy of the GNU General Public License
|
|
|
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
#
|
|
|
|
|
|
|
|
from pySim.commands import SimCardCommands
|
|
|
|
from pySim.filesystem import CardApplication, interpret_sw
|
|
|
|
from pySim.utils import all_subclasses
|
|
|
|
import abc
|
|
|
|
import operator
|
|
|
|
|
|
|
|
def _mf_select_test(scc:SimCardCommands, cla_byte:str, sel_ctrl:str) -> bool:
|
|
|
|
cla_byte_bak = scc.cla_byte
|
|
|
|
sel_ctrl_bak = scc.sel_ctrl
|
|
|
|
scc.reset_card()
|
|
|
|
|
|
|
|
scc.cla_byte = cla_byte
|
|
|
|
scc.sel_ctrl = sel_ctrl
|
|
|
|
rc = True
|
|
|
|
try:
|
|
|
|
scc.select_file('3f00')
|
|
|
|
except:
|
|
|
|
rc = False
|
|
|
|
|
|
|
|
scc.reset_card()
|
|
|
|
scc.cla_byte = cla_byte_bak
|
|
|
|
scc.sel_ctrl = sel_ctrl_bak
|
|
|
|
return rc
|
|
|
|
|
|
|
|
def match_uicc(scc:SimCardCommands) -> bool:
|
|
|
|
""" Try to access MF via UICC APDUs (3GPP TS 102.221), if this works, the
|
|
|
|
card is considered a UICC card.
|
|
|
|
"""
|
|
|
|
return _mf_select_test(scc, "00", "0004")
|
|
|
|
|
|
|
|
def match_sim(scc:SimCardCommands) -> bool:
|
|
|
|
""" Try to access MF via 2G APDUs (3GPP TS 11.11), if this works, the card
|
|
|
|
is also a simcard. This will be the case for most UICC cards, but there may
|
|
|
|
also be plain UICC cards without 2G support as well.
|
|
|
|
"""
|
|
|
|
return _mf_select_test(scc, "a0", "0000")
|
|
|
|
|
|
|
|
class CardProfile(object):
|
|
|
|
"""A Card Profile describes a card, it's filesystem hierarchy, an [initial] list of
|
|
|
|
applications as well as profile-specific SW and shell commands. Every card has
|
|
|
|
one card profile, but there may be multiple applications within that profile."""
|
|
|
|
|
|
|
|
def __init__(self, name, **kw):
|
|
|
|
"""
|
|
|
|
Args:
|
|
|
|
desc (str) : Description
|
|
|
|
files_in_mf : List of CardEF instances present in MF
|
|
|
|
applications : List of CardApplications present on card
|
|
|
|
sw : List of status word definitions
|
|
|
|
shell_cmdsets : List of cmd2 shell command sets of profile-specific commands
|
|
|
|
cla : class byte that should be used with cards of this profile
|
|
|
|
sel_ctrl : selection control bytes class byte that should be used with cards of this profile
|
|
|
|
"""
|
|
|
|
self.name = name
|
|
|
|
self.desc = kw.get("desc", None)
|
|
|
|
self.files_in_mf = kw.get("files_in_mf", [])
|
|
|
|
self.sw = kw.get("sw", {})
|
|
|
|
self.applications = kw.get("applications", [])
|
|
|
|
self.shell_cmdsets = kw.get("shell_cmdsets", [])
|
|
|
|
self.cla = kw.get("cla", "00")
|
|
|
|
self.sel_ctrl = kw.get("sel_ctrl", "0004")
|
|
|
|
|
|
|
|
def __str__(self):
|
|
|
|
return self.name
|
|
|
|
|
|
|
|
def add_application(self, app:CardApplication):
|
|
|
|
"""Add an application to a card profile.
|
|
|
|
|
|
|
|
Args:
|
|
|
|
app : CardApplication instance to be added to profile
|
|
|
|
"""
|
|
|
|
self.applications.append(app)
|
|
|
|
|
|
|
|
def interpret_sw(self, sw:str):
|
|
|
|
"""Interpret a given status word within the profile.
|
|
|
|
|
|
|
|
Args:
|
|
|
|
sw : Status word as string of 4 hex digits
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
Tuple of two strings
|
|
|
|
"""
|
|
|
|
return interpret_sw(self.sw, sw)
|
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
def decode_select_response(data_hex:str) -> object:
|
|
|
|
"""Decode the response to a SELECT command.
|
|
|
|
|
|
|
|
This is the fall-back method which doesn't perform any decoding. It mostly
|
|
|
|
exists so specific derived classes can overload it for actual decoding.
|
|
|
|
This method is implemented in the profile and is only used when application
|
|
|
|
specific decoding cannot be performed (no ADF is selected).
|
|
|
|
|
|
|
|
Args:
|
|
|
|
data_hex: Hex string of the select response
|
|
|
|
"""
|
|
|
|
return data_hex
|
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
@abc.abstractmethod
|
|
|
|
def match_with_card(scc:SimCardCommands) -> bool:
|
|
|
|
"""Check if the specific profile matches the card. This method is a
|
|
|
|
placeholder that is overloaded by specific dirived classes. The method
|
|
|
|
actively probes the card to make sure the profile class matches the
|
|
|
|
physical card. This usually also means that the card is reset during
|
|
|
|
the process, so this method must not be called at random times. It may
|
|
|
|
only be called on startup.
|
|
|
|
|
|
|
|
Args:
|
|
|
|
scc: SimCardCommands class
|
|
|
|
Returns:
|
|
|
|
match = True, no match = False
|
|
|
|
"""
|
|
|
|
return False
|
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
def pick(scc:SimCardCommands):
|
|
|
|
profiles = list(all_subclasses(CardProfile))
|
|
|
|
profiles.sort(key=operator.attrgetter('ORDER'))
|
|
|
|
|
|
|
|
for p in profiles:
|
|
|
|
if p.match_with_card(scc):
|
|
|
|
return p()
|
|
|
|
|
|
|
|
return None
|