Change in pysim[master]: pySim-shell: add method to match card profile to card

This is merely a historical archive of years 2008-2021, before the migration to mailman3.

A maintained and still updated list archive can be found at https://lists.osmocom.org/hyperkitty/list/gerrit-log@lists.osmocom.org/.

laforge gerrit-no-reply at lists.osmocom.org
Mon Nov 22 15:45:15 UTC 2021


laforge has submitted this change. ( https://gerrit.osmocom.org/c/pysim/+/26165 )

Change subject: pySim-shell: add method to match card profile to card
......................................................................

pySim-shell: add method to match card profile to card

UICC and old SIM cards can be difficult to tell apart without prior
knowledge of the card. The ATR won't tell if the card is UICC or not.
The only remaining option is to try out if the card is able to handle
UICC APDUs. The same is true for 2G SIM cards. It is not guranteed that
every UICC card will have 2G functionality.

Lets add functionality to match a profile to the currently plugged card
by actively probing it.

Lets also add another profile to distinguish between UICC-only cards and
UICC cards that include SIM functionality.

Change-Id: If090d32551145f75c644657b90085a3ef5bfa691
Related: OS#5274
---
M pySim-shell.py
M pySim/filesystem.py
A pySim/profile.py
M pySim/ts_102_221.py
M pySim/ts_51_011.py
M pySim/utils.py
6 files changed, 216 insertions(+), 70 deletions(-)

Approvals:
  Jenkins Builder: Verified
  laforge: Looks good to me, approved



diff --git a/pySim-shell.py b/pySim-shell.py
index 128c0ea..0519ec4 100755
--- a/pySim-shell.py
+++ b/pySim-shell.py
@@ -45,8 +45,10 @@
 from pySim.card_handler import CardHandler, CardHandlerAuto
 
 from pySim.filesystem import CardMF, RuntimeState, CardDF, CardADF, CardModel
+from pySim.profile import CardProfile
 from pySim.ts_51_011 import CardProfileSIM, DF_TELECOM, DF_GSM
 from pySim.ts_102_221 import CardProfileUICC
+from pySim.ts_102_221 import CardProfileUICCSIM
 from pySim.ts_31_102 import CardApplicationUSIM
 from pySim.ts_31_103 import CardApplicationISIM
 from pySim.ara_m import CardApplicationARAM
@@ -80,19 +82,32 @@
 
 	card = card_detect("auto", scc)
 	if card is None:
-		print("Could not detect card type!")
+		print("Warning: Could not detect card type - assuming a generic card type...")
+		card = SimCard(scc)
+
+	profile = CardProfile.pick(scc)
+	if profile is None:
+		print("Unsupported card type!")
 		return None, None
 
+	print("Info: Card is of type: %s" % str(profile))
+
+	# FIXME: This shouln't be here, the profile should add the applications,
+	# however, we cannot simply put his into ts_102_221.py since we would
+	# have to e.g. import CardApplicationUSIM from ts_31_102.py, which already
+	# imports from ts_102_221.py. This means we will end up with a circular
+	# import, which needs to be resolved first.
+	if isinstance(profile, CardProfileUICC):
+		profile.add_application(CardApplicationUSIM())
+		profile.add_application(CardApplicationISIM())
+		profile.add_application(CardApplicationARAM())
+
 	# Create runtime state with card profile
-	profile = CardProfileUICC()
-	profile.add_application(CardApplicationUSIM())
-	profile.add_application(CardApplicationISIM())
-	profile.add_application(CardApplicationARAM())
 	rs = RuntimeState(card, profile)
 
-	# FIXME: do this dynamically
-	rs.mf.add_file(DF_TELECOM())
-	rs.mf.add_file(DF_GSM())
+	# FIXME: This is an GSM-R related file, it needs to be added throughout,
+	# the profile. At the moment we add it for all cards, this won't hurt,
+	# but regular SIM and UICC will not have it and fail to select it.
 	rs.mf.add_file(DF_EIRENE())
 
 	CardModel.apply_matching_models(scc, rs)
diff --git a/pySim/filesystem.py b/pySim/filesystem.py
index 0238c97..6b20db5 100644
--- a/pySim/filesystem.py
+++ b/pySim/filesystem.py
@@ -1443,65 +1443,6 @@
         """
         return interpret_sw(self.sw, sw)
 
-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)
-
-    def decode_select_response(self, data_hex:str) -> Any:
-        """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
-
 
 class CardModel(abc.ABC):
     """A specific card model, typically having some additional vendor-specific files. All
diff --git a/pySim/profile.py b/pySim/profile.py
new file mode 100644
index 0000000..f068d7c
--- /dev/null
+++ b/pySim/profile.py
@@ -0,0 +1,148 @@
+# -*- 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
+from typing import Any
+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)
+
+	def decode_select_response(self, data_hex:str) -> Any:
+		"""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
diff --git a/pySim/ts_102_221.py b/pySim/ts_102_221.py
index 61e236e..53cd118 100644
--- a/pySim/ts_102_221.py
+++ b/pySim/ts_102_221.py
@@ -23,6 +23,13 @@
 from pySim.utils import *
 from pySim.filesystem import *
 from bidict import bidict
+from pySim.profile import CardProfile
+from pySim.profile import match_uicc
+from pySim.profile import match_sim
+
+# A UICC will usually also support 2G functionality. If this is the case, we
+# need to add DF_GSM and DF_TELECOM along with the UICC related files
+from pySim.ts_51_011 import DF_GSM, DF_TELECOM
 
 ts_102_22x_cmdset = CardCommandSet('TS 102 22x', [
     # TS 102 221 Section 10.1.2 Table 10.5 "Coding of Instruction Byte"
@@ -603,9 +610,11 @@
         addl_info = FlagsEnum(Byte, req_inc_idle_current=1, support_uicc_suspend=2)
         self._construct = Struct('max_current_mA'/Int8ub, 't_op_s'/Int8ub, 'addl_info'/addl_info)
 
-
 class CardProfileUICC(CardProfile):
-    def __init__(self):
+
+    ORDER = 1
+
+    def __init__(self, name = 'UICC'):
         files = [
             EF_DIR(),
             EF_ICCID(),
@@ -683,7 +692,27 @@
             },
           }
 
-        super().__init__('UICC', desc='ETSI TS 102 221', cla="00", sel_ctrl="0004", files_in_mf=files, sw=sw)
+        super().__init__(name, desc='ETSI TS 102 221', cla="00", sel_ctrl="0004", files_in_mf=files, sw=sw)
 
     def decode_select_response(self, data_hex:str) -> Any:
         return decode_select_response(data_hex)
+
+    @staticmethod
+    def match_with_card(scc:SimCardCommands) -> bool:
+        return match_uicc(scc)
+
+class CardProfileUICCSIM(CardProfileUICC):
+    """Same as above, but including 2G SIM support"""
+
+    ORDER = 0
+
+    def __init__(self):
+        super().__init__('UICC-SIM')
+
+        # Add GSM specific files
+        self.files_in_mf.append(DF_TELECOM())
+        self.files_in_mf.append(DF_GSM())
+
+    @staticmethod
+    def match_with_card(scc:SimCardCommands) -> bool:
+        return match_uicc(scc) and match_sim(scc)
diff --git a/pySim/ts_51_011.py b/pySim/ts_51_011.py
index 73d569e..393277b 100644
--- a/pySim/ts_51_011.py
+++ b/pySim/ts_51_011.py
@@ -332,6 +332,8 @@
 import enum
 
 from pySim.filesystem import *
+from pySim.profile import CardProfile
+from pySim.profile import match_sim
 
 ######################################################################
 # DF.TELECOM
@@ -975,6 +977,9 @@
     return ret
 
 class CardProfileSIM(CardProfile):
+
+    ORDER = 2
+
     def __init__(self):
         sw = {
           'Normal': {
@@ -1016,3 +1021,7 @@
         super().__init__('SIM', desc='GSM SIM Card', cla="a0", sel_ctrl="0000", files_in_mf=[DF_TELECOM(), DF_GSM()], sw=sw)
     def decode_select_response(self, data_hex:str) -> Any:
 	    return _decode_select_response(data_hex)
+
+    @staticmethod
+    def match_with_card(scc:SimCardCommands) -> bool:
+        return match_sim(scc)
diff --git a/pySim/utils.py b/pySim/utils.py
index 521abd6..e30c970 100644
--- a/pySim/utils.py
+++ b/pySim/utils.py
@@ -1530,3 +1530,7 @@
         if cla and not cmd.match_cla(cla):
             return None
         return cmd
+
+def all_subclasses(cls) -> set:
+	"""Recursively get all subclasses of a specified class"""
+	return set(cls.__subclasses__()).union([s for c in cls.__subclasses__() for s in all_subclasses(c)])

-- 
To view, visit https://gerrit.osmocom.org/c/pysim/+/26165
To unsubscribe, or for help writing mail filters, visit https://gerrit.osmocom.org/settings

Gerrit-Project: pysim
Gerrit-Branch: master
Gerrit-Change-Id: If090d32551145f75c644657b90085a3ef5bfa691
Gerrit-Change-Number: 26165
Gerrit-PatchSet: 9
Gerrit-Owner: dexter <pmaier at sysmocom.de>
Gerrit-Reviewer: Jenkins Builder
Gerrit-Reviewer: daniel <dwillmann at sysmocom.de>
Gerrit-Reviewer: fixeria <vyanitskiy at sysmocom.de>
Gerrit-Reviewer: laforge <laforge at osmocom.org>
Gerrit-Reviewer: osmith <osmith at sysmocom.de>
Gerrit-MessageType: merged
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.osmocom.org/pipermail/gerrit-log/attachments/20211122/a0cbf520/attachment.htm>


More information about the gerrit-log mailing list