aboutsummaryrefslogtreecommitdiffstats
path: root/pySim/profile.py
blob: e464e1f9b98168f111ae6a9a106ac38085d96d16 (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
150
151
152
153
154
155
156
157
158
159
160
161
162
163
# -*- 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
import abc
import operator
from typing import List


def _mf_select_test(scc: SimCardCommands,
                    cla_byte: str, sel_ctrl: str,
                    fids: List[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:
        for fid in fids:
            scc.select_file(fid)
    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", ["3f00"])


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", ["3f00"])


def match_ruim(scc: SimCardCommands) -> bool:
    """ Try to access MF/DF.CDMA via 2G APDUs (3GPP TS 11.11), if this works,
    the card is considered an R-UIM card for CDMA.
    """
    return _mf_select_test(scc, "a0", "0000", ["3f00", "7f25"])


class CardProfile:
    """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) -> object:
        """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