feat: basic replay functionality
This commit is contained in:
17
.vscode/launch.json
vendored
Normal file
17
.vscode/launch.json
vendored
Normal file
@@ -0,0 +1,17 @@
|
||||
{
|
||||
// Use IntelliSense to learn about possible attributes.
|
||||
// Hover to view descriptions of existing attributes.
|
||||
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"name": "reSIMulate: record",
|
||||
"type": "debugpy",
|
||||
"request": "launch",
|
||||
"program": "${workspaceFolder}/resimulate/resimulate.py",
|
||||
"console": "integratedTerminal",
|
||||
"args": ["record", "-o test.pkl"],
|
||||
"justMyCode": false
|
||||
}
|
||||
]
|
||||
}
|
||||
68
poetry.lock
generated
68
poetry.lock
generated
@@ -558,14 +558,14 @@ windows-terminal = ["colorama (>=0.4.6)"]
|
||||
|
||||
[[package]]
|
||||
name = "pyosmocom"
|
||||
version = "0.0.7"
|
||||
version = "0.0.8"
|
||||
description = "Python implementation of core osmocom utilities / protocols"
|
||||
optional = false
|
||||
python-versions = ">=3.9"
|
||||
groups = ["main"]
|
||||
files = [
|
||||
{file = "pyosmocom-0.0.7-py3-none-any.whl", hash = "sha256:6abcd35b7ecca8e8c5edd1d3f5a508c8692a2084a27aca488a4ff487fad7e317"},
|
||||
{file = "pyosmocom-0.0.7.tar.gz", hash = "sha256:d6fcab6234fb5007af5dd4bc676ff29397dc40a53756997e64493e3e335502b4"},
|
||||
{file = "pyosmocom-0.0.8-py3-none-any.whl", hash = "sha256:eaa09524bb031b0408ce69dba4ce0f60525c016b8033e92aaa5475483ad5a472"},
|
||||
{file = "pyosmocom-0.0.8.tar.gz", hash = "sha256:d008d64d6423dd79980b1e8df4ab751eb68acb48a3e18c976e293d71b50d36f0"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
@@ -601,29 +601,30 @@ dev = ["build", "flake8", "mypy", "pytest", "twine"]
|
||||
|
||||
[[package]]
|
||||
name = "pyscard"
|
||||
version = "2.2.0"
|
||||
version = "2.2.1"
|
||||
description = "Smartcard module for Python."
|
||||
optional = false
|
||||
python-versions = ">=3.9"
|
||||
groups = ["main"]
|
||||
files = [
|
||||
{file = "pyscard-2.2.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:05cfa9840b3f4b08769487e4d84b1432d9d913035a3726856329c2648444a6ba"},
|
||||
{file = "pyscard-2.2.0-cp310-cp310-win32.whl", hash = "sha256:c363a36a803cd3e6334546d661c0b1375c16ab42600bc4be18ade3ed70eae1a6"},
|
||||
{file = "pyscard-2.2.0-cp310-cp310-win_amd64.whl", hash = "sha256:001e760f42d2f9b7b6aba6b83fca67314e925d6df1ded75689c5ef5377f6e701"},
|
||||
{file = "pyscard-2.2.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:51758a04e70d07b233a5d7ed58492007bbbebcb3f47130a638edbe021b82df86"},
|
||||
{file = "pyscard-2.2.0-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:a138707d8ef5c53da4e7a86d1e604e2cba42fe92249099a67374624503825326"},
|
||||
{file = "pyscard-2.2.0-cp311-cp311-win_amd64.whl", hash = "sha256:45ec2a19cbc40dc56c9831f9979c81617a3a89265b1430708e1c137a82ec5c83"},
|
||||
{file = "pyscard-2.2.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:9a3d47a6799efbc8e6124638730a86b705ddecfba92874aec6b79348e764fe8a"},
|
||||
{file = "pyscard-2.2.0-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:fe8625b7cb1be2af0fe5d90d6daabb26a65aeb7d601455446c6a06341a2f0814"},
|
||||
{file = "pyscard-2.2.0-cp312-cp312-win32.whl", hash = "sha256:b0b476691652bb641b175d7d345bb27639615c6a5bf140e63a057750aefc1bd9"},
|
||||
{file = "pyscard-2.2.0-cp312-cp312-win_amd64.whl", hash = "sha256:9ce06b7007147f7346114274dd20edd0399e9aedbdadcc9cf4d0c867bec1cae1"},
|
||||
{file = "pyscard-2.2.0-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:5296c705d2f5b6947f6929a63d55521a380125c0160679b8069a11107c8b94ec"},
|
||||
{file = "pyscard-2.2.0-cp313-cp313-win32.whl", hash = "sha256:6cdb99c1d51f625fe246df57b65fad6d834a094fa5efd36d0c40356916c19431"},
|
||||
{file = "pyscard-2.2.0-cp313-cp313-win_amd64.whl", hash = "sha256:ff6f2b8ef00b48fa5f1448f0c7d812f677ce98a9994e917500ecf00e3f31dc89"},
|
||||
{file = "pyscard-2.2.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:0f23309f4f98ae41c836090c3449d8fb7470c19d57adda2592803e1df668bece"},
|
||||
{file = "pyscard-2.2.0-cp39-cp39-win32.whl", hash = "sha256:5857d60b44bbb074c9b174111fe94af4179b882942e53292de16f0c798f50c5d"},
|
||||
{file = "pyscard-2.2.0-cp39-cp39-win_amd64.whl", hash = "sha256:e4e664b1d3b87956104b87094f81a98bdd6ac40986b52d0c40451de1fc98ca49"},
|
||||
{file = "pyscard-2.2.0.tar.gz", hash = "sha256:6aa194d4bb295e78a97056dd1d32273cc69ddbe3c852aad60a8578f04017a1bf"},
|
||||
{file = "pyscard-2.2.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:e0e85b8a0a316690be7d5b2e85302651f0443db271a8ca77cb09f8a6fdd6ebfa"},
|
||||
{file = "pyscard-2.2.1-cp310-cp310-win32.whl", hash = "sha256:fcac209666236bd08d876834f8233046713139490b46a37baa19e4eee959d003"},
|
||||
{file = "pyscard-2.2.1-cp310-cp310-win_amd64.whl", hash = "sha256:4a638bc33d6c6b4a95ac4e7b5eb4426b3e1d0c5b74e018a650294473456b43c3"},
|
||||
{file = "pyscard-2.2.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:21b458aef2baaf97a3dc719b7d6d94d9333d5cff703536688ee9881993607328"},
|
||||
{file = "pyscard-2.2.1-cp311-cp311-win32.whl", hash = "sha256:4bec90a07e25220a6eeaa2a6d6e45c60ce40b5e0f0c7059f03f16357c01e66a8"},
|
||||
{file = "pyscard-2.2.1-cp311-cp311-win_amd64.whl", hash = "sha256:aa886dda9d6dfc9acb58902dca1db6da7cf22a6dd284ded08ad6bf1ac7b207fc"},
|
||||
{file = "pyscard-2.2.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:cc2157faa3e75bfb81f393215c2fb980757a5f466015acb6cd221769ba5fc52d"},
|
||||
{file = "pyscard-2.2.1-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:8a682ecb2ac56d88f265f2bc0afe032f8ffc2ec544f384d7a0cdea2d08cae35c"},
|
||||
{file = "pyscard-2.2.1-cp312-cp312-win32.whl", hash = "sha256:0a96499b4e910ad4a8244d5406dc6afb4c91812e1124b992832b48e9e4d93b7e"},
|
||||
{file = "pyscard-2.2.1-cp312-cp312-win_amd64.whl", hash = "sha256:7acb878270ee58379474646fa6be17dd101b8d870eeadb004f3c2cad736b0b09"},
|
||||
{file = "pyscard-2.2.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:803b52f04389456a011ae8b3c02bcdc232e69b32c137b8b5c8695430bb2006af"},
|
||||
{file = "pyscard-2.2.1-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:dc926ba396c1fa6e4db1007b9e62b2f330c79c943c3bbbb893908f589071fc86"},
|
||||
{file = "pyscard-2.2.1-cp313-cp313-win32.whl", hash = "sha256:2aca599294cd5178d359627b9b58a8e636ea3ebd73b70da79a95c684be985c83"},
|
||||
{file = "pyscard-2.2.1-cp313-cp313-win_amd64.whl", hash = "sha256:9c33a6cd75479f3f2e1f812399d228e371f030d6f0630b69c34a02519bbf6dca"},
|
||||
{file = "pyscard-2.2.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:affafc324ed4a8e735c392e7576f8b55c26a7f2c4f529e3ccb2adf88531d85a4"},
|
||||
{file = "pyscard-2.2.1-cp39-cp39-win32.whl", hash = "sha256:b209b6f4c996f9c4a7e04fb3e3e8210f4175986b2bc36ef5d735667d53071381"},
|
||||
{file = "pyscard-2.2.1-cp39-cp39-win_amd64.whl", hash = "sha256:4b7615ff1c6b620136ce554e528affe93bdd9f705601ac23453c2c5d56bf979b"},
|
||||
{file = "pyscard-2.2.1.tar.gz", hash = "sha256:920e688a5108224cb19b915c3fd7ea7cf3d1aa379587ffd087973e84c13f8d94"},
|
||||
]
|
||||
|
||||
[package.extras]
|
||||
@@ -670,7 +671,7 @@ optional = false
|
||||
python-versions = "*"
|
||||
groups = ["main"]
|
||||
files = []
|
||||
develop = false
|
||||
develop = true
|
||||
|
||||
[package.dependencies]
|
||||
bidict = "*"
|
||||
@@ -689,10 +690,8 @@ pyyaml = ">=5.1"
|
||||
termcolor = "*"
|
||||
|
||||
[package.source]
|
||||
type = "git"
|
||||
url = "https://github.com/osmocom/pySim.git"
|
||||
reference = "HEAD"
|
||||
resolved_reference = "712946eddb9eedce30b44276d7bd75f40c9a69b9"
|
||||
type = "directory"
|
||||
url = "../pysim"
|
||||
|
||||
[[package]]
|
||||
name = "pytlv"
|
||||
@@ -787,6 +786,21 @@ pygments = ">=2.13.0,<3.0.0"
|
||||
[package.extras]
|
||||
jupyter = ["ipywidgets (>=7.5.1,<9)"]
|
||||
|
||||
[[package]]
|
||||
name = "rich-argparse"
|
||||
version = "1.6.0"
|
||||
description = "Rich help formatters for argparse and optparse"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
groups = ["main"]
|
||||
files = [
|
||||
{file = "rich_argparse-1.6.0-py3-none-any.whl", hash = "sha256:fbe70a1d821b3f2fa8958cddf0cae131870a6e9faa04ab52b409cb1eda809bd7"},
|
||||
{file = "rich_argparse-1.6.0.tar.gz", hash = "sha256:092083c30da186f25bcdff8b1d47fdfb571288510fb051e0488a72cc3128de13"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
rich = ">=11.0.0"
|
||||
|
||||
[[package]]
|
||||
name = "six"
|
||||
version = "1.17.0"
|
||||
@@ -845,4 +859,4 @@ files = [
|
||||
[metadata]
|
||||
lock-version = "2.1"
|
||||
python-versions = ">=3.13"
|
||||
content-hash = "d787a3424fd3e8689e3e2b99f4e6225e7f78400d0fe0eb11117972c96d9ce99d"
|
||||
content-hash = "0f33c17a00302b58f79d1748001e128c11b8f27149fd66a329fd2c8c1990cf3d"
|
||||
|
||||
@@ -9,9 +9,10 @@ readme = "README.md"
|
||||
requires-python = ">=3.13"
|
||||
dependencies = [
|
||||
"rich (>=13.9.4,<14.0.0)",
|
||||
"pysim @ git+https://github.com/osmocom/pySim.git",
|
||||
"pyshark (>=0.6,<0.7)",
|
||||
"argcomplete (>=3.5.3,<4.0.0)",
|
||||
"rich-argparse (>=1.6.0,<2.0.0)",
|
||||
"pysim @ file:///home/niklas/Documents/documents/uni/master_thesis/pysim",
|
||||
]
|
||||
|
||||
|
||||
@@ -21,3 +22,6 @@ build-backend = "poetry.core.masonry.api"
|
||||
|
||||
[tool.poetry.group.dev.dependencies]
|
||||
black = "^24.10.0"
|
||||
|
||||
[tool.poetry.dependencies]
|
||||
pysim = {develop = true}
|
||||
|
||||
0
resimulate/__init__.py
Normal file
0
resimulate/__init__.py
Normal file
@@ -1,24 +1,18 @@
|
||||
import pickle
|
||||
from queue import Empty, Queue
|
||||
import signal
|
||||
from queue import Empty, Queue, ShutDown
|
||||
from threading import Thread
|
||||
import time
|
||||
|
||||
from pySim.apdu import Apdu, ApduCommand
|
||||
from pySim.apdu_source.gsmtap import ApduSource
|
||||
from rich.align import Align
|
||||
from rich.console import Group
|
||||
from rich.live import Live
|
||||
from rich.progress import BarColumn, Progress, TextColumn, TimeElapsedColumn
|
||||
from rich.text import Text
|
||||
from util.logger import log
|
||||
from util.tracer import Tracer
|
||||
|
||||
from rich.console import Group
|
||||
from rich.panel import Panel
|
||||
from rich.progress import (
|
||||
BarColumn,
|
||||
Progress,
|
||||
SpinnerColumn,
|
||||
TextColumn,
|
||||
TimeElapsedColumn,
|
||||
)
|
||||
|
||||
|
||||
class Recorder:
|
||||
def __init__(self, source: ApduSource):
|
||||
@@ -30,6 +24,10 @@ class Recorder:
|
||||
self.tracer_thread = Thread(
|
||||
target=self.tracer.main, args=(self.package_queue,), daemon=True
|
||||
)
|
||||
signal.signal(signal.SIGINT, self.__signal_handler)
|
||||
|
||||
def __signal_handler(self, sig, frame):
|
||||
self.package_queue.shutdown(immediate=True)
|
||||
|
||||
def record(self, output_path: str, timeout: int):
|
||||
capture_progress = Progress(
|
||||
@@ -47,8 +45,12 @@ class Recorder:
|
||||
)
|
||||
|
||||
main_group = Group(
|
||||
Panel(capture_progress),
|
||||
# Panel(capture_progress, title="APDU Packets captured", expand=False),
|
||||
overall_progress,
|
||||
Align.left(
|
||||
Text.assemble("Press ", ("Ctrl+C", "bold red"), " to stop capturing."),
|
||||
vertical="bottom",
|
||||
),
|
||||
)
|
||||
|
||||
overall_task_id = overall_progress.add_task(
|
||||
@@ -57,7 +59,7 @@ class Recorder:
|
||||
total=None,
|
||||
)
|
||||
|
||||
with Live(main_group):
|
||||
with Live(main_group) as live:
|
||||
self.tracer_thread.start()
|
||||
|
||||
while self.tracer_thread.is_alive():
|
||||
@@ -69,23 +71,28 @@ class Recorder:
|
||||
log.debug("No more APDU packets to capture.")
|
||||
break
|
||||
|
||||
capture_task_id = capture_progress.add_task(
|
||||
log.info("Captured %s %s", apdu_command._name, apdu)
|
||||
|
||||
""" capture_task_id = capture_progress.add_task(
|
||||
"",
|
||||
completed=len(self.captured_apdus),
|
||||
packet_type=str(apdu_command._name) or "",
|
||||
packet_description=str(apdu_command.path_str),
|
||||
packet_code=str(apdu_command.col_sw),
|
||||
)
|
||||
) """
|
||||
|
||||
self.captured_apdus.append(apdu)
|
||||
|
||||
capture_progress.stop_task(capture_task_id)
|
||||
""" capture_progress.stop_task(capture_task_id) """
|
||||
except TimeoutError:
|
||||
log.debug("Timeout reached, stopping capture.")
|
||||
break
|
||||
except Empty:
|
||||
log.debug("No more APDU packets to capture.")
|
||||
break
|
||||
except ShutDown:
|
||||
log.debug("Shutting down capture.")
|
||||
break
|
||||
except UnboundLocalError as e:
|
||||
log.debug("Error capturing APDU packets: %s", e)
|
||||
break
|
||||
@@ -95,8 +102,6 @@ class Recorder:
|
||||
description=f"[bold green]{len(self.captured_apdus)} packet(s) captured!",
|
||||
)
|
||||
|
||||
capture_progress.stop_task(capture_task_id)
|
||||
|
||||
overall_progress.update(
|
||||
overall_task_id,
|
||||
description="[bold yellow]Saving captured APDU commands...",
|
||||
@@ -106,7 +111,6 @@ class Recorder:
|
||||
|
||||
with open(output_path, "wb") as f:
|
||||
pickle.dump(self.captured_apdus, f)
|
||||
time.sleep(3)
|
||||
|
||||
overall_progress.update(
|
||||
overall_task_id,
|
||||
|
||||
@@ -1 +1,53 @@
|
||||
# https://github.com/Textualize/rich/blob/master/examples/dynamic_progress.py
|
||||
import pickle
|
||||
|
||||
from osmocom.utils import b2h
|
||||
from pySim.apdu import Apdu
|
||||
from pySim.app import init_card
|
||||
from pySim.card_handler import CardHandler
|
||||
from rich.align import Align
|
||||
from rich.console import Group
|
||||
from rich.live import Live
|
||||
from rich.progress import BarColumn, Progress, TextColumn, TimeElapsedColumn
|
||||
from rich.text import Text
|
||||
from util.logger import log
|
||||
from util.pcsc_link import PcscLink
|
||||
|
||||
|
||||
class Replayer:
|
||||
def __init__(self, device: int):
|
||||
self.pcsc_link = PcscLink(device=device)
|
||||
self.card_handler = CardHandler(self.pcsc_link)
|
||||
self.runtime_state, self.card = init_card(self.card_handler)
|
||||
|
||||
def replay(self, input_path: str):
|
||||
progress = Progress(
|
||||
TimeElapsedColumn(),
|
||||
BarColumn(),
|
||||
TextColumn("{task.description}"),
|
||||
)
|
||||
|
||||
main_group = Group(
|
||||
progress,
|
||||
Align.left(
|
||||
Text.assemble(
|
||||
"Press ", ("Ctrl+C", "bold red"), " to stop replaying APDUs."
|
||||
),
|
||||
vertical="bottom",
|
||||
),
|
||||
)
|
||||
|
||||
progress_id = progress.add_task(
|
||||
f"[bold red]Loading APDUs from {input_path}...", start=True, total=None
|
||||
)
|
||||
|
||||
with Live(main_group):
|
||||
with open(input_path, "rb") as f:
|
||||
apdus: list[Apdu] = pickle.load(f)
|
||||
|
||||
with self.pcsc_link as link:
|
||||
for id, apdu in enumerate(apdus):
|
||||
progress.update(
|
||||
progress_id, description=f"Replaying APDU {id} / {len(apdus)}"
|
||||
)
|
||||
data, sw = link.send_tpdu(b2h(apdu))
|
||||
log.debug("APDU: %s, SW: %s", b2h(apdu), sw)
|
||||
|
||||
2
resimulate/exceptions.py
Normal file
2
resimulate/exceptions.py
Normal file
@@ -0,0 +1,2 @@
|
||||
class PcscError(Exception):
|
||||
pass
|
||||
@@ -4,13 +4,14 @@
|
||||
import argparse
|
||||
|
||||
import argcomplete
|
||||
from pySim.apdu_source.gsmtap import GsmtapApduSource
|
||||
|
||||
from commands.record import Recorder
|
||||
from commands.replay import Replayer
|
||||
from pySim.apdu_source.gsmtap import GsmtapApduSource
|
||||
from rich_argparse import RichHelpFormatter
|
||||
|
||||
parser = argparse.ArgumentParser(
|
||||
description="ReSIMulate is a terminal application built for eSIM and SIM-specific APDU analysis. It captures APDU commands, saves them, and replays them to facilitate differential testing, ensuring accurate validation and debugging of SIM interactions.",
|
||||
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
|
||||
formatter_class=RichHelpFormatter,
|
||||
)
|
||||
|
||||
subparsers = parser.add_subparsers(
|
||||
@@ -65,11 +66,11 @@ replay_parser.add_argument(
|
||||
help="File containing APDU commands to replay (e.g., 'commands.apdu').",
|
||||
)
|
||||
replay_parser.add_argument(
|
||||
"-d",
|
||||
"--device",
|
||||
type=str,
|
||||
default="default_device",
|
||||
help="Target simtrace device to send APDU commands (default: 'default_device').",
|
||||
"-p",
|
||||
"--pcsc-device",
|
||||
type=int,
|
||||
default=0,
|
||||
help="Target simtrace device to send APDU commands (default: 0).",
|
||||
)
|
||||
|
||||
if __name__ == "__main__":
|
||||
@@ -83,7 +84,8 @@ if __name__ == "__main__":
|
||||
recorder.record(args.output, args.timeout)
|
||||
|
||||
elif args.command == "replay":
|
||||
pass
|
||||
replayer = Replayer(args.pcsc_device)
|
||||
replayer.replay(args.input.name)
|
||||
|
||||
else:
|
||||
raise ValueError(f"Unsupported command: {args.command}")
|
||||
|
||||
26
resimulate/util/apdu_highlighter.py
Normal file
26
resimulate/util/apdu_highlighter.py
Normal file
@@ -0,0 +1,26 @@
|
||||
from rich.highlighter import RegexHighlighter
|
||||
|
||||
|
||||
class ApduHighlighter(RegexHighlighter):
|
||||
|
||||
base_style = "apdu."
|
||||
highlights = [
|
||||
# Class name (e.g., Apdu)
|
||||
r"(?P<class_name>\w+)\(",
|
||||
# CLA (2 hex digits)
|
||||
r"(?P<cla>[0-9A-F]{2})\s",
|
||||
# INS (2 hex digits)
|
||||
r"(?P<ins>[0-9A-F]{2})\s",
|
||||
# P1 (2 hex digits)
|
||||
r"(?P<p1>[0-9A-F]{2})\s",
|
||||
# P2 (2 hex digits)
|
||||
r"(?P<p2>[0-9A-F]{2})\s",
|
||||
# P3 (2 hex digits)
|
||||
r"(?P<p3>[0-9A-F]{2})\s",
|
||||
# Command Data (hex bytes separated by spaces)
|
||||
r"(?P<cmd_data>(?:[0-9A-F]{2}\s?)+)",
|
||||
# Response Data (hex bytes separated by spaces)
|
||||
r"(?P<rsp_data>(?:[0-9A-F]{2}\s?)+)",
|
||||
# SW (4 hex digits)
|
||||
r"(?P<sw>[0-9A-F]{4})\)",
|
||||
]
|
||||
@@ -1,6 +1,8 @@
|
||||
import logging
|
||||
from rich.logging import RichHandler
|
||||
|
||||
from rich.console import Console
|
||||
from rich.logging import RichHandler
|
||||
from util.apdu_highlighter import ApduHighlighter
|
||||
|
||||
|
||||
class RichLogger:
|
||||
@@ -22,7 +24,9 @@ class RichLogger:
|
||||
self.logger = logging.getLogger("rich")
|
||||
|
||||
def get_logger(self, console: Console = None) -> logging.Logger:
|
||||
if console:
|
||||
if not console:
|
||||
console = Console(highlighter=ApduHighlighter())
|
||||
|
||||
self._initialize(console)
|
||||
return self.logger
|
||||
|
||||
|
||||
98
resimulate/util/pcsc_link.py
Normal file
98
resimulate/util/pcsc_link.py
Normal file
@@ -0,0 +1,98 @@
|
||||
from exceptions import PcscError
|
||||
from osmocom.utils import Hexstr, h2i, i2h
|
||||
from pySim.transport import LinkBaseTpdu
|
||||
from pySim.utils import ResTuple
|
||||
from smartcard import System
|
||||
from smartcard.CardConnection import CardConnection
|
||||
from smartcard.CardRequest import CardRequest
|
||||
from smartcard.Exceptions import (
|
||||
CardConnectionException,
|
||||
CardRequestTimeoutException,
|
||||
NoCardException,
|
||||
)
|
||||
from smartcard.ExclusiveConnectCardConnection import ExclusiveConnectCardConnection
|
||||
from util.logger import log
|
||||
|
||||
|
||||
class PcscLink(LinkBaseTpdu):
|
||||
protocol = CardConnection.T0_protocol
|
||||
|
||||
def __init__(self, device: int, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
|
||||
readers = System.readers()
|
||||
if device > len(readers):
|
||||
raise PcscError(f"Device with index {device} not found.")
|
||||
|
||||
self.pcsc_device = readers[device]
|
||||
self.card_connection = ExclusiveConnectCardConnection(
|
||||
self.pcsc_device.createConnection()
|
||||
)
|
||||
|
||||
def __str__(self) -> str:
|
||||
return "PCSC[%s]" % (self._reader)
|
||||
|
||||
def __del__(self):
|
||||
try:
|
||||
self.card_connection.disconnect()
|
||||
except:
|
||||
pass
|
||||
|
||||
def __enter__(self):
|
||||
self.connect()
|
||||
return self
|
||||
|
||||
def __exit__(self, exc_type, exc_value, traceback):
|
||||
self.card_connection.disconnect()
|
||||
log.debug("Disconnected from device %s", self.pcsc_device)
|
||||
|
||||
def connect(self):
|
||||
try:
|
||||
self.card_connection.disconnect()
|
||||
self.card_connection.connect()
|
||||
supported_protocols = self.card_connection.getSupportedProtocols()
|
||||
self.card_connection.disconnect()
|
||||
|
||||
if supported_protocols & CardConnection.T0_protocol:
|
||||
protocol = CardConnection.T0_protocol
|
||||
elif supported_protocols & CardConnection.T1_protocol:
|
||||
protocol = CardConnection.T1_protocol
|
||||
else:
|
||||
raise PcscError("No supported protocol found.")
|
||||
|
||||
log.debug(
|
||||
"Connecting to device %s using protocol %s", self.pcsc_device, protocol
|
||||
)
|
||||
|
||||
self.card_connection.connect(protocol=protocol)
|
||||
except (CardConnectionException, NoCardException) as e:
|
||||
raise PcscError from e
|
||||
|
||||
log.debug("Connected to device %s", self.pcsc_device)
|
||||
|
||||
def disconnect(self):
|
||||
self.card_connection.disconnect()
|
||||
log.debug("Disconnected from device %s", self.pcsc_device)
|
||||
|
||||
def _reset_card(self):
|
||||
self.disconnect()
|
||||
self.connect()
|
||||
|
||||
def wait_for_card(self, timeout: int | None = None, newcardonly: bool = False):
|
||||
card_request = CardRequest(
|
||||
readers=[self.pcsc_device], timeout=timeout, newcardonly=newcardonly
|
||||
)
|
||||
try:
|
||||
log.debug("Waiting for card on device %s", self.pcsc_device)
|
||||
card_request.waitforcard()
|
||||
except CardRequestTimeoutException as e:
|
||||
raise PcscError from e
|
||||
|
||||
self.__connect()
|
||||
|
||||
def send_tpdu(self, tpdu: Hexstr) -> ResTuple:
|
||||
try:
|
||||
data, sw1, sw2 = self.card_connection.transmit(h2i(tpdu))
|
||||
return i2h(data) + i2h([sw1, sw2])
|
||||
except CardConnectionException as e:
|
||||
raise PcscError from e
|
||||
@@ -1,22 +1,27 @@
|
||||
from queue import Queue
|
||||
|
||||
from pySim.apdu import ApduDecoder, CardReset
|
||||
from pySim.apdu.global_platform import ApduCommands as GlobalPlatformCommands
|
||||
from pySim.apdu.ts_31_102 import ApduCommands as UsimApduCommands
|
||||
from pySim.apdu.ts_102_221 import ApduCommands as UiccApduCommands
|
||||
from pySim.apdu.ts_102_221 import UiccSelect, UiccStatus
|
||||
from pySim.apdu.ts_102_222 import ApduCommands as ManageApduCommands
|
||||
from pySim.apdu_source import ApduSource
|
||||
from pySim.ara_m import CardApplicationARAM
|
||||
from pySim.cards import UiccCardBase
|
||||
from pySim.commands import SimCardCommands
|
||||
from pySim.euicc import CardApplicationECASD, CardApplicationISDR
|
||||
from pySim.global_platform import CardApplicationISD
|
||||
from pySim.runtime import RuntimeState
|
||||
from pySim.ts_31_102 import CardApplicationUSIM
|
||||
from pySim.ts_31_103 import CardApplicationISIM
|
||||
from pySim.ts_102_221 import CardProfileUICC
|
||||
|
||||
from util.dummy_sim_link import DummySimLink
|
||||
from util.logger import log
|
||||
|
||||
APDU_COMMANDS = UiccApduCommands + UsimApduCommands
|
||||
APDU_COMMANDS = (
|
||||
UiccApduCommands + UsimApduCommands + ManageApduCommands + GlobalPlatformCommands
|
||||
)
|
||||
|
||||
|
||||
# Taken from the pySim project and modified for the ReSIMulate project
|
||||
@@ -29,6 +34,8 @@ class Tracer:
|
||||
profile.add_application(CardApplicationISIM())
|
||||
profile.add_application(CardApplicationISDR())
|
||||
profile.add_application(CardApplicationECASD())
|
||||
profile.add_application(CardApplicationARAM())
|
||||
profile.add_application(CardApplicationISD())
|
||||
|
||||
scc = SimCardCommands(transport=DummySimLink())
|
||||
card = UiccCardBase(scc)
|
||||
@@ -53,7 +60,7 @@ class Tracer:
|
||||
package_queue.task_done()
|
||||
return
|
||||
except Exception as e:
|
||||
log.error("Error reading APDU: %s", e)
|
||||
log.error("Error reading APDU (%s): %s", apdu, e)
|
||||
continue
|
||||
|
||||
if apdu is None:
|
||||
@@ -71,10 +78,10 @@ class Tracer:
|
||||
try:
|
||||
apdu_command.process(self.runtime_state)
|
||||
except ValueError as e:
|
||||
log.error("Error processing APDU: %s", e)
|
||||
log.error("Error reading APDU (%s): %s", apdu, e)
|
||||
continue
|
||||
except AttributeError as e:
|
||||
log.error("Error processing APDU: %s", e)
|
||||
log.error("Error processing APDU (%s): %s", apdu, e)
|
||||
return
|
||||
|
||||
# Avoid cluttering the log with too much verbosity
|
||||
|
||||
Reference in New Issue
Block a user