Source code for pyecsca.sca.target.ISO7816
"""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 enum import IntEnum
from typing import Optional
from public import public
from .base import Target
[docs]
@public
class CardConnectionException(Exception):
"""Card could not be connected."""
pass
[docs]
@public
class CardProtocol(IntEnum):
"""Card protocol to use/negotiate."""
T0 = 0
T1 = 1
[docs]
@public
@dataclass
class CommandAPDU: # pragma: no cover
"""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])
)
)
[docs]
@public
@dataclass
class ResponseAPDU:
"""Response APDU that can be received from an ISO7816-4 target."""
data: bytes
sw: int
[docs]
@public
class ISO7816Target(Target, ABC):
"""ISO7816-4 target."""
[docs]
@abstractmethod
def connect(self, protocol: Optional[CardProtocol] = None):
"""
Connect to the card.
:param protocol: CardProtocol to use.
"""
raise NotImplementedError
@property
@abstractmethod
def atr(self) -> bytes:
"""Return the ATR (Answer To Reset) of the target."""
raise NotImplementedError
[docs]
@abstractmethod
def select(self, aid: bytes) -> bool:
"""
Select an applet with :paramref:`~.select.aid`.
:param aid: The AID of the applet to select.
:return: Whether the selection was successful.
"""
raise NotImplementedError
[docs]
@abstractmethod
def send_apdu(self, apdu: CommandAPDU) -> ResponseAPDU:
"""
Send an APDU to the selected applet.
:param apdu: The APDU to send.
:return: The response.
"""
raise NotImplementedError
[docs]
@public
class ISO7816:
"""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