aboutsummaryrefslogtreecommitdiff
path: root/pyecsca/sca/target/ISO7816.py
blob: 1a9c8b8aa182001b8eb92bcf8620e6c72dfcc2ef (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
"""
This module provides classes for working with ISO7816-4 APDUs and an abstract base class for an ISO7816-4 based target.
"""
from abc import abstractmethod, ABC
from dataclasses import dataclass
from typing import Optional

from public import public

from .base import Target


@public
@dataclass
class CommandAPDU(object):  # pragma: no cover
    """A command APDU that can be sent to an ISO7816-4 target."""

    cls: int
    ins: int
    p1: int
    p2: int
    data: Optional[bytes] = None
    ne: Optional[int] = None

    def __bytes__(self):
        if self.data is None or len(self.data) == 0:
            if self.ne is None or self.ne == 0:
                # Case 1
                return bytes([self.cls, self.ins, self.p1, self.p2])
            elif self.ne <= 256:
                # Case 2s
                return bytes(
                    [
                        self.cls,
                        self.ins,
                        self.p1,
                        self.p2,
                        self.ne if self.ne != 256 else 0,
                    ]
                )
            else:
                # Case 2e
                return bytes([self.cls, self.ins, self.p1, self.p2]) + (
                    self.ne.to_bytes(2, "big") if self.ne != 65536 else bytes([0, 0])
                )
        elif self.ne is None or self.ne == 0:
            if len(self.data) <= 255:
                # Case 3s
                return (
                    bytes([self.cls, self.ins, self.p1, self.p2, len(self.data)])
                    + self.data
                )
            else:
                # Case 3e
                return (
                    bytes([self.cls, self.ins, self.p1, self.p2, 0])
                    + len(self.data).to_bytes(2, "big")
                    + self.data
                )
        else:
            if len(self.data) <= 255 and self.ne <= 256:
                # Case 4s
                return (
                    bytes([self.cls, self.ins, self.p1, self.p2, len(self.data)])
                    + self.data
                    + bytes([self.ne if self.ne != 256 else 0])
                )
            else:
                # Case 4e
                return (
                    bytes([self.cls, self.ins, self.p1, self.p2, 0])
                    + len(self.data).to_bytes(2, "big")
                    + self.data
                    + (
                        self.ne.to_bytes(2, "big")
                        if self.ne != 65536
                        else bytes([0, 0])
                    )
                )


@public
@dataclass
class ResponseAPDU(object):
    """A response APDU that can be received from an ISO7816-4 target."""

    data: bytes
    sw: int


@public
class ISO7816Target(Target, ABC):
    """An ISO7816-4 target."""

    @property
    @abstractmethod
    def atr(self) -> bytes:
        """The ATR (Answer To Reset) of the target."""
        ...

    @abstractmethod
    def select(self, aid: bytes) -> bool:
        """
        Select an applet with `aid`.

        :param aid: The AID of the applet to select.
        :return: Whether the selection was successful.
        """
        ...

    @abstractmethod
    def send_apdu(self, apdu: CommandAPDU) -> ResponseAPDU:
        """
        Send an APDU to the selected applet.

        :param apdu: The APDU to send.
        :return: The response.
        """
        ...


@public
class ISO7816:
    """A bunch of ISO7816-4 constants (status words)."""

    SW_FILE_FULL = 0x6A84
    SW_UNKNOWN = 0x6F00
    SW_CLA_NOT_SUPPORTED = 0x6E00
    SW_INS_NOT_SUPPORTED = 0x6D00
    SW_CORRECT_LENGTH_00 = 0x6C00
    SW_WRONG_P1P2 = 0x6B00
    SW_INCORRECT_P1P2 = 0x6A86
    SW_RECORD_NOT_FOUND = 0x6A83
    SW_FILE_NOT_FOUND = 0x6A82
    SW_FUNC_NOT_SUPPORTED = 0x6A81
    SW_WRONG_DATA = 0x6A80
    SW_APPLET_SELECT_FAILED = 0x6999
    SW_COMMAND_NOT_ALLOWED = 0x6986
    SW_CONDITIONS_NOT_SATISFIED = 0x6985
    SW_DATA_INVALID = 0x6984
    SW_FILE_INVALID = 0x6983
    SW_SECURITY_STATUS_NOT_SATISFIED = 0x6982
    SW_WRONG_LENGTH = 0x6700
    SW_BYTES_REMAINING_00 = 0x6100
    SW_NO_ERROR = 0x9000