blob: 768064f25c64b6dbdcfc2580164a1f74dc178b88 [file] [log] [blame]
Philipp Maiera028c7d2021-11-08 16:12:03 +01001# -*- coding: utf-8 -*-
2
3""" pySim: tell old 2G SIMs apart from UICC
4"""
5
6#
7# (C) 2021 by Sysmocom s.f.m.c. GmbH
8# All Rights Reserved
9#
10# This program is free software: you can redistribute it and/or modify
11# it under the terms of the GNU General Public License as published by
12# the Free Software Foundation, either version 2 of the License, or
13# (at your option) any later version.
14#
15# This program is distributed in the hope that it will be useful,
16# but WITHOUT ANY WARRANTY; without even the implied warranty of
17# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18# GNU General Public License for more details.
19#
20# You should have received a copy of the GNU General Public License
21# along with this program. If not, see <http://www.gnu.org/licenses/>.
22#
23
24from pySim.commands import SimCardCommands
25from pySim.filesystem import CardApplication, interpret_sw
26from pySim.utils import all_subclasses
27from typing import Any
28import abc
29import operator
30
31def _mf_select_test(scc:SimCardCommands, cla_byte:str, sel_ctrl:str) -> bool:
32 cla_byte_bak = scc.cla_byte
33 sel_ctrl_bak = scc.sel_ctrl
34 scc.reset_card()
35
36 scc.cla_byte = cla_byte
37 scc.sel_ctrl = sel_ctrl
38 rc = True
39 try:
40 scc.select_file('3f00')
41 except:
42 rc = False
43
44 scc.reset_card()
45 scc.cla_byte = cla_byte_bak
46 scc.sel_ctrl = sel_ctrl_bak
47 return rc
48
49def match_uicc(scc:SimCardCommands) -> bool:
50 """ Try to access MF via UICC APDUs (3GPP TS 102.221), if this works, the
51 card is considered a UICC card.
52 """
53 return _mf_select_test(scc, "00", "0004")
54
55def match_sim(scc:SimCardCommands) -> bool:
56 """ Try to access MF via 2G APDUs (3GPP TS 11.11), if this works, the card
57 is also a simcard. This will be the case for most UICC cards, but there may
58 also be plain UICC cards without 2G support as well.
59 """
60 return _mf_select_test(scc, "a0", "0000")
61
62class CardProfile(object):
63 """A Card Profile describes a card, it's filesystem hierarchy, an [initial] list of
64 applications as well as profile-specific SW and shell commands. Every card has
65 one card profile, but there may be multiple applications within that profile."""
66
67 def __init__(self, name, **kw):
68 """
69 Args:
70 desc (str) : Description
71 files_in_mf : List of CardEF instances present in MF
72 applications : List of CardApplications present on card
73 sw : List of status word definitions
74 shell_cmdsets : List of cmd2 shell command sets of profile-specific commands
75 cla : class byte that should be used with cards of this profile
76 sel_ctrl : selection control bytes class byte that should be used with cards of this profile
77 """
78 self.name = name
79 self.desc = kw.get("desc", None)
80 self.files_in_mf = kw.get("files_in_mf", [])
81 self.sw = kw.get("sw", {})
82 self.applications = kw.get("applications", [])
83 self.shell_cmdsets = kw.get("shell_cmdsets", [])
84 self.cla = kw.get("cla", "00")
85 self.sel_ctrl = kw.get("sel_ctrl", "0004")
86
87 def __str__(self):
88 return self.name
89
90 def add_application(self, app:CardApplication):
91 """Add an application to a card profile.
92
93 Args:
94 app : CardApplication instance to be added to profile
95 """
96 self.applications.append(app)
97
98 def interpret_sw(self, sw:str):
99 """Interpret a given status word within the profile.
100
101 Args:
102 sw : Status word as string of 4 hex digits
103
104 Returns:
105 Tuple of two strings
106 """
107 return interpret_sw(self.sw, sw)
108
Philipp Maier5998a3a2021-11-16 15:16:39 +0100109 @staticmethod
110 def decode_select_response(data_hex:str) -> Any:
Philipp Maiera028c7d2021-11-08 16:12:03 +0100111 """Decode the response to a SELECT command.
112
113 This is the fall-back method which doesn't perform any decoding. It mostly
114 exists so specific derived classes can overload it for actual decoding.
115 This method is implemented in the profile and is only used when application
116 specific decoding cannot be performed (no ADF is selected).
117
118 Args:
119 data_hex: Hex string of the select response
120 """
121 return data_hex
122
123 @staticmethod
124 @abc.abstractmethod
125 def match_with_card(scc:SimCardCommands) -> bool:
126 """Check if the specific profile matches the card. This method is a
127 placeholder that is overloaded by specific dirived classes. The method
128 actively probes the card to make sure the profile class matches the
129 physical card. This usually also means that the card is reset during
130 the process, so this method must not be called at random times. It may
131 only be called on startup.
132
133 Args:
134 scc: SimCardCommands class
135 Returns:
136 match = True, no match = False
137 """
138 return False
139
140 @staticmethod
141 def pick(scc:SimCardCommands):
142 profiles = list(all_subclasses(CardProfile))
143 profiles.sort(key=operator.attrgetter('ORDER'))
144
145 for p in profiles:
146 if p.match_with_card(scc):
147 return p()
148
149 return None