aboutsummaryrefslogtreecommitdiffstats
path: root/pySim/profile.py
blob: 768064f25c64b6dbdcfc2580164a1f74dc178b88 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
# -*- 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)

	@staticmethod
	def decode_select_response(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