blob: f068d7cd0cbab0060d27d6fdc03ed69fd03dd9f1 [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
109 def decode_select_response(self, data_hex:str) -> Any:
110 """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