blob: 8f3e986c3860082e0238968552e767218c60de4b [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
Philipp Maiera028c7d2021-11-08 16:12:03 +010027import abc
28import operator
29
30def _mf_select_test(scc:SimCardCommands, cla_byte:str, sel_ctrl:str) -> bool:
31 cla_byte_bak = scc.cla_byte
32 sel_ctrl_bak = scc.sel_ctrl
33 scc.reset_card()
34
35 scc.cla_byte = cla_byte
36 scc.sel_ctrl = sel_ctrl
37 rc = True
38 try:
39 scc.select_file('3f00')
40 except:
41 rc = False
42
43 scc.reset_card()
44 scc.cla_byte = cla_byte_bak
45 scc.sel_ctrl = sel_ctrl_bak
46 return rc
47
48def match_uicc(scc:SimCardCommands) -> bool:
49 """ Try to access MF via UICC APDUs (3GPP TS 102.221), if this works, the
50 card is considered a UICC card.
51 """
52 return _mf_select_test(scc, "00", "0004")
53
54def match_sim(scc:SimCardCommands) -> bool:
55 """ Try to access MF via 2G APDUs (3GPP TS 11.11), if this works, the card
56 is also a simcard. This will be the case for most UICC cards, but there may
57 also be plain UICC cards without 2G support as well.
58 """
59 return _mf_select_test(scc, "a0", "0000")
60
61class CardProfile(object):
62 """A Card Profile describes a card, it's filesystem hierarchy, an [initial] list of
63 applications as well as profile-specific SW and shell commands. Every card has
64 one card profile, but there may be multiple applications within that profile."""
65
66 def __init__(self, name, **kw):
67 """
68 Args:
69 desc (str) : Description
70 files_in_mf : List of CardEF instances present in MF
71 applications : List of CardApplications present on card
72 sw : List of status word definitions
73 shell_cmdsets : List of cmd2 shell command sets of profile-specific commands
74 cla : class byte that should be used with cards of this profile
75 sel_ctrl : selection control bytes class byte that should be used with cards of this profile
76 """
77 self.name = name
78 self.desc = kw.get("desc", None)
79 self.files_in_mf = kw.get("files_in_mf", [])
80 self.sw = kw.get("sw", {})
81 self.applications = kw.get("applications", [])
82 self.shell_cmdsets = kw.get("shell_cmdsets", [])
83 self.cla = kw.get("cla", "00")
84 self.sel_ctrl = kw.get("sel_ctrl", "0004")
85
86 def __str__(self):
87 return self.name
88
89 def add_application(self, app:CardApplication):
90 """Add an application to a card profile.
91
92 Args:
93 app : CardApplication instance to be added to profile
94 """
95 self.applications.append(app)
96
97 def interpret_sw(self, sw:str):
98 """Interpret a given status word within the profile.
99
100 Args:
101 sw : Status word as string of 4 hex digits
102
103 Returns:
104 Tuple of two strings
105 """
106 return interpret_sw(self.sw, sw)
107
Philipp Maier5998a3a2021-11-16 15:16:39 +0100108 @staticmethod
Philipp Maier9e42e7f2021-11-16 15:46:42 +0100109 def decode_select_response(data_hex:str) -> object:
Philipp Maiera028c7d2021-11-08 16:12:03 +0100110 """Decode the response to a SELECT command.
111
112 This is the fall-back method which doesn't perform any decoding. It mostly
113 exists so specific derived classes can overload it for actual decoding.
114 This method is implemented in the profile and is only used when application
115 specific decoding cannot be performed (no ADF is selected).
116
117 Args:
118 data_hex: Hex string of the select response
119 """
120 return data_hex
121
122 @staticmethod
123 @abc.abstractmethod
124 def match_with_card(scc:SimCardCommands) -> bool:
125 """Check if the specific profile matches the card. This method is a
126 placeholder that is overloaded by specific dirived classes. The method
127 actively probes the card to make sure the profile class matches the
128 physical card. This usually also means that the card is reset during
129 the process, so this method must not be called at random times. It may
130 only be called on startup.
131
132 Args:
133 scc: SimCardCommands class
134 Returns:
135 match = True, no match = False
136 """
137 return False
138
139 @staticmethod
140 def pick(scc:SimCardCommands):
141 profiles = list(all_subclasses(CardProfile))
142 profiles.sort(key=operator.attrgetter('ORDER'))
143
144 for p in profiles:
145 if p.match_with_card(scc):
146 return p()
147
148 return None