15 Commits

Author SHA1 Message Date
Vadim Yanitskiy
8f38800643 pySim-shell.py: make it work with cmd2 >= v2.4.0
In v2.3.0 both cmd2.{fg,bg} have been deprecated in favour of cmd2.{Fg,Bg}.
In v2.4.0 both cmd2.{fg,bg} have been removed.

See https://github.com/python-cmd2/cmd2/blob/master/CHANGELOG.md

Change-Id: I7ca95e85fc45ba66fd9ba6bea1fec2bae0e892c0
2022-09-05 23:27:43 +07:00
Vadim Yanitskiy
d5c1bec869 pySim-shell.py: make it work with cmd2 >= v2.0.0
* Argument 'use_ipython' was renamed to 'use_ipython'.
* Class 'Settable' requires the reference to the object that holds
  the settable attribute.

See https://github.com/python-cmd2/cmd2/releases/tag/2.0.0.

Change-Id: Ia38f0ca5c3f41395f8fe850adae37f5af4e3fe19
2022-09-05 23:27:43 +07:00
Vadim Yanitskiy
7d05e49f11 README.md: update installation instructions for Debian
Change-Id: Icefa33570a34960a4fff145f3c1b6585d867605c
2022-09-05 23:15:11 +07:00
Vadim Yanitskiy
98ea2a0f7a README.md: update git URLs (git -> https; gitea)
Change-Id: Ia86979f656557e442b0f432b0646aa7661c293e9
2022-09-05 23:15:11 +07:00
Vadim Yanitskiy
0a8d27ad7a README.md: list recent dependencies from requirements.txt
Change-Id: Ia486dbc7f630c1404e51728b5353cf5a0d643415
2022-09-05 23:15:11 +07:00
Vadim Yanitskiy
9550a0a45b README.md: fix module name: s/serial/pyserial/
Change-Id: I5fd308fb161cd5bd5f702845691296877e523248
2022-09-05 23:15:11 +07:00
Vadim Yanitskiy
b5eaf14991 README.md,requirements.txt: add missing construct version info
Change-Id: I90da0df431f0d7dbfa4aa428366fbf0e35db388f
Related: OS#5666
2022-09-05 23:15:10 +07:00
Vadim Yanitskiy
bdac3f61be Bump minimum required construct version to v2.9.51
With this version I can get all unittests passing:

  python -m unittest discover tests/

We're passing argument 'path' to stream_read_entire(), which was
added in [1] and become available since v2.9.51.

Change-Id: I4223c83570d333ad8d79bc2aa2d8bcc580156cff
Related: [1] bfe71315b027e18e62f00ec4de75043992fd2316 construct.git
Related: OS#5666
2022-09-05 23:15:10 +07:00
Vadim Yanitskiy
05d30eb666 construct: use Python's API for int<->bytes conversion
Argument 'signed' was added in [1] and become available since v2.10.63.
Therefore using bytes2integer() and integer2bytes() from construct.core
bumps the minimum required version of construct to v2.10.63.  For
instance, debian:bullseye currently ships v2.10.58.

There is no strict requirement to use construct's API, so let's use
Python's API instead.  This allows using older construct versions
from the v2.9.xx family.

Change-Id: I613dbfebe993f9c19003635371941710fc1b1236
Related: [1] 660ddbe2d9a351731ad7976351adbf413809a715 construct.git
Related: OS#5666
2022-09-05 23:15:10 +07:00
Vadim Yanitskiy
7800f9d356 contrib/jenkins.sh: install dependencies from requirements.txt
Change-Id: I99af496e9a3758ea624ca484f4fbc51b262ffaf4
2022-09-05 23:15:10 +07:00
Vadim Yanitskiy
7ce04a5a29 contrib/jenkins.sh: execute this script with -x and -e
-x  Print commands and their arguments as they are executed
  -e  Exit immediately if a command exits with a non-zero status

Change-Id: I13af70ef770936bec00b050b6c4f988e53ee2833
2022-09-05 23:15:06 +07:00
Vadim Yanitskiy
b3ea021b32 contrib/jenkins.sh: speed up pylint by running multiple processes
Use multiple processes to speed up pylint.  Specifying -j0 will
auto-detect the number of processors available to use.

On AMD Ryzen 7 3700X this significantly reduces the exec time:

  $ time python -m pylint -j1 ... pySim *.py
  real    0m12.409s
  user    0m12.149s
  sys     0m0.136s

  $ time python -m pylint -j0 ... pySim *.py
  real    0m5.541s
  user    0m58.496s
  sys     0m1.213s

Change-Id: I76d1696c27ddcab358526f807c4a0a7f0d4c85d4
2022-08-30 17:15:53 +07:00
Vadim Yanitskiy
12175d3588 contrib/jenkins.sh: pylint v2.15 is unstable, pin v2.14.5
pylint v2.15 is crashing, let's fall-back to a known to work v2.14.5.

Change-Id: Ie29be6ec6631ff2b3d8cd6b2dd9ac0ed8f505e4f
Related: https://github.com/PyCQA/pylint/issues/7375
Related: OS#5668
2022-08-30 17:12:03 +07:00
Christian Amsüss
59f3b1154f proactive: Send a Terminal Response automatically after a Fetch
Change-Id: I43bc994e7517b5907fb40a98d84797c54056c47d
2022-08-21 11:54:33 +00:00
Christian Amsüss
98552ef1bd proactive: Avoid clobbering the output of the command that triggered the FETCH
Change-Id: I2b794a5c5bc808b9703b4bc679c119341a0ed41c
2022-08-21 11:54:00 +00:00
7 changed files with 95 additions and 56 deletions

View File

@@ -23,10 +23,10 @@ Git Repository
You can clone from the official Osmocom git repository using You can clone from the official Osmocom git repository using
``` ```
git clone git://git.osmocom.org/pysim.git git clone https://gitea.osmocom.org/sim-card/pysim.git
``` ```
There is a cgit interface at <https://git.osmocom.org/pysim> There is a web interface at <https://gitea.osmocom.org/sim-card/pysim>.
Installation Installation
@@ -35,18 +35,26 @@ Installation
Please install the following dependencies: Please install the following dependencies:
- pyscard - pyscard
- serial - pyserial
- pytlv - pytlv
- cmd2 >= 1.3.0 but < 2.0.0 - cmd2 >= 1.3.0 but < 2.0.0
- jsonpath-ng - jsonpath-ng
- construct - construct >= 2.9.51
- bidict - bidict
- gsm0338 - gsm0338
- pyyaml >= 5.1
- termcolor
- colorlog
Example for Debian: Example for Debian:
``` ```sh
apt-get install python3-pyscard python3-serial python3-pip python3-yaml python3-termcolor python3-colorlog sudo apt-get install --no-install-recommends \
pip3 install -r requirements.txt pcscd libpcsclite-dev \
python3 \
python3-setuptools \
python3-pyscard \
python3-pip
pip3 install --user -r requirements.txt
``` ```
After installing all dependencies, the pySim applications ``pySim-read.py``, ``pySim-prog.py`` and ``pySim-shell.py`` may be started directly from the cloned repository. After installing all dependencies, the pySim applications ``pySim-read.py``, ``pySim-prog.py`` and ``pySim-shell.py`` may be started directly from the cloned repository.

View File

@@ -1,4 +1,4 @@
#!/bin/sh #!/bin/sh -xe
# jenkins build helper script for pysim. This is how we build on jenkins.osmocom.org # jenkins build helper script for pysim. This is how we build on jenkins.osmocom.org
# #
# environment variables: # environment variables:
@@ -6,8 +6,6 @@
# * PUBLISH: upload manuals after building if set to "1" (ignored without WITH_MANUALS = "1") # * PUBLISH: upload manuals after building if set to "1" (ignored without WITH_MANUALS = "1")
# #
set -e
if [ ! -d "./pysim-testdata/" ] ; then if [ ! -d "./pysim-testdata/" ] ; then
echo "###############################################" echo "###############################################"
echo "Please call from pySim-prog top directory" echo "Please call from pySim-prog top directory"
@@ -17,15 +15,7 @@ fi
virtualenv -p python3 venv --system-site-packages virtualenv -p python3 venv --system-site-packages
. venv/bin/activate . venv/bin/activate
pip install pytlv pip install -r requirements.txt
pip install 'pyyaml>=5.1'
pip install cmd2==1.5
pip install jsonpath-ng
pip install construct
pip install bidict
pip install gsm0338
pip install termcolor
pip install colorlog
# Execute automatically discovered unit tests first # Execute automatically discovered unit tests first
python -m unittest discover -v -s tests/ python -m unittest discover -v -s tests/
@@ -36,8 +26,8 @@ python -m unittest discover -v -s tests/
# Ignore E0401: import-error # Ignore E0401: import-error
# pySim/utils.py:276: E0401: Unable to import 'Crypto.Cipher' (import-error) # pySim/utils.py:276: E0401: Unable to import 'Crypto.Cipher' (import-error)
# pySim/utils.py:277: E0401: Unable to import 'Crypto.Util.strxor' (import-error) # pySim/utils.py:277: E0401: Unable to import 'Crypto.Util.strxor' (import-error)
pip install pylint pip install pylint==2.14.5 # FIXME: 2.15 is crashing, see OS#5668
python -m pylint --errors-only \ python -m pylint -j0 --errors-only \
--disable E1102 \ --disable E1102 \
--disable E0401 \ --disable E0401 \
--enable W0301 \ --enable W0301 \

View File

@@ -23,7 +23,7 @@ import json
import traceback import traceback
import cmd2 import cmd2
from cmd2 import style, fg from cmd2 import style, Fg
from cmd2 import CommandSet, with_default_category, with_argparser from cmd2 import CommandSet, with_default_category, with_argparser
import argparse import argparse
@@ -134,8 +134,8 @@ class PysimApp(cmd2.Cmd):
def __init__(self, card, rs, sl, ch, script=None): def __init__(self, card, rs, sl, ch, script=None):
super().__init__(persistent_history_file='~/.pysim_shell_history', allow_cli_args=False, super().__init__(persistent_history_file='~/.pysim_shell_history', allow_cli_args=False,
use_ipython=True, auto_load_commands=False, startup_script=script) auto_load_commands=False, startup_script=script)
self.intro = style('Welcome to pySim-shell!', fg=fg.red) self.intro = style('Welcome to pySim-shell!', fg=Fg.RED)
self.default_category = 'pySim-shell built-in commands' self.default_category = 'pySim-shell built-in commands'
self.card = None self.card = None
self.rs = None self.rs = None
@@ -145,16 +145,16 @@ class PysimApp(cmd2.Cmd):
self.ch = ch self.ch = ch
self.numeric_path = False self.numeric_path = False
self.add_settable(cmd2.Settable('numeric_path', bool, 'Print File IDs instead of names', self.add_settable(cmd2.Settable('numeric_path', bool, 'Print File IDs instead of names', self,
onchange_cb=self._onchange_numeric_path)) onchange_cb=self._onchange_numeric_path))
self.conserve_write = True self.conserve_write = True
self.add_settable(cmd2.Settable('conserve_write', bool, 'Read and compare before write', self.add_settable(cmd2.Settable('conserve_write', bool, 'Read and compare before write', self,
onchange_cb=self._onchange_conserve_write)) onchange_cb=self._onchange_conserve_write))
self.json_pretty_print = True self.json_pretty_print = True
self.add_settable(cmd2.Settable('json_pretty_print', self.add_settable(cmd2.Settable('json_pretty_print',
bool, 'Pretty-Print JSON output')) bool, 'Pretty-Print JSON output', self))
self.apdu_trace = False self.apdu_trace = False
self.add_settable(cmd2.Settable('apdu_trace', bool, 'Trace and display APDUs exchanged with card', self.add_settable(cmd2.Settable('apdu_trace', bool, 'Trace and display APDUs exchanged with card', self,
onchange_cb=self._onchange_apdu_trace)) onchange_cb=self._onchange_apdu_trace))
self.equip(card, rs) self.equip(card, rs)
@@ -287,23 +287,23 @@ class PysimApp(cmd2.Cmd):
sys.stderr = self._stderr_backup sys.stderr = self._stderr_backup
def _show_failure_sign(self): def _show_failure_sign(self):
self.poutput(style(" +-------------+", fg=fg.bright_red)) self.poutput(style(" +-------------+", fg=Fg.LIGHT_RED))
self.poutput(style(" + ## ## +", fg=fg.bright_red)) self.poutput(style(" + ## ## +", fg=Fg.LIGHT_RED))
self.poutput(style(" + ## ## +", fg=fg.bright_red)) self.poutput(style(" + ## ## +", fg=Fg.LIGHT_RED))
self.poutput(style(" + ### +", fg=fg.bright_red)) self.poutput(style(" + ### +", fg=Fg.LIGHT_RED))
self.poutput(style(" + ## ## +", fg=fg.bright_red)) self.poutput(style(" + ## ## +", fg=Fg.LIGHT_RED))
self.poutput(style(" + ## ## +", fg=fg.bright_red)) self.poutput(style(" + ## ## +", fg=Fg.LIGHT_RED))
self.poutput(style(" +-------------+", fg=fg.bright_red)) self.poutput(style(" +-------------+", fg=Fg.LIGHT_RED))
self.poutput("") self.poutput("")
def _show_success_sign(self): def _show_success_sign(self):
self.poutput(style(" +-------------+", fg=fg.bright_green)) self.poutput(style(" +-------------+", fg=Fg.LIGHT_GREEN))
self.poutput(style(" + ## +", fg=fg.bright_green)) self.poutput(style(" + ## +", fg=Fg.LIGHT_GREEN))
self.poutput(style(" + ## +", fg=fg.bright_green)) self.poutput(style(" + ## +", fg=Fg.LIGHT_GREEN))
self.poutput(style(" + # ## +", fg=fg.bright_green)) self.poutput(style(" + # ## +", fg=Fg.LIGHT_GREEN))
self.poutput(style(" + ## # +", fg=fg.bright_green)) self.poutput(style(" + ## # +", fg=Fg.LIGHT_GREEN))
self.poutput(style(" + ## +", fg=fg.bright_green)) self.poutput(style(" + ## +", fg=Fg.LIGHT_GREEN))
self.poutput(style(" +-------------+", fg=fg.bright_green)) self.poutput(style(" +-------------+", fg=Fg.LIGHT_GREEN))
self.poutput("") self.poutput("")
def _process_card(self, first, script_path): def _process_card(self, first, script_path):

View File

@@ -2,7 +2,7 @@ from construct.lib.containers import Container, ListContainer
from construct.core import EnumIntegerString from construct.core import EnumIntegerString
import typing import typing
from construct import * from construct import *
from construct.core import evaluate, bytes2integer, integer2bytes, BitwisableString from construct.core import evaluate, BitwisableString
from construct.lib import integertypes from construct.lib import integertypes
from pySim.utils import b2h, h2b, swap_nibbles from pySim.utils import b2h, h2b, swap_nibbles
import gsm0338 import gsm0338
@@ -219,7 +219,7 @@ class GreedyInteger(Construct):
if evaluate(self.swapped, context): if evaluate(self.swapped, context):
data = swapbytes(data) data = swapbytes(data)
try: try:
return bytes2integer(data, self.signed) return int.from_bytes(data, byteorder='big', signed=self.signed)
except ValueError as e: except ValueError as e:
raise IntegerError(str(e), path=path) raise IntegerError(str(e), path=path)
@@ -248,7 +248,7 @@ class GreedyInteger(Construct):
raise IntegerError(f"value {obj} is not an integer", path=path) raise IntegerError(f"value {obj} is not an integer", path=path)
length = self.__bytes_required(obj, self.minlen) length = self.__bytes_required(obj, self.minlen)
try: try:
data = integer2bytes(obj, length, self.signed) data = obj.to_bytes(length, byteorder='big', signed=self.signed)
except ValueError as e: except ValueError as e:
raise IntegerError(str(e), path=path) raise IntegerError(str(e), path=path)
if evaluate(self.swapped, context): if evaluate(self.swapped, context):

View File

@@ -10,7 +10,7 @@ from typing import Optional, Tuple
from pySim.exceptions import * from pySim.exceptions import *
from pySim.construct import filter_dict from pySim.construct import filter_dict
from pySim.utils import sw_match, b2h, h2b, i2h, Hexstr from pySim.utils import sw_match, b2h, h2b, i2h, Hexstr
from pySim.cat import ProactiveCommand from pySim.cat import ProactiveCommand, CommandDetails, DeviceIdentities, Result
# #
# Copyright (C) 2009-2010 Sylvain Munaut <tnt@246tNt.com> # Copyright (C) 2009-2010 Sylvain Munaut <tnt@246tNt.com>
@@ -42,10 +42,7 @@ class ProactiveHandler(abc.ABC):
"""Abstract base class representing the interface of some code that handles """Abstract base class representing the interface of some code that handles
the proactive commands, as returned by the card in responses to the FETCH the proactive commands, as returned by the card in responses to the FETCH
command.""" command."""
def receive_fetch_raw(self, payload: Hexstr): def receive_fetch_raw(self, pcmd: ProactiveCommand, parsed: Hexstr):
# parse the proactive command
pcmd = ProactiveCommand()
parsed = pcmd.from_tlv(h2b(payload))
# try to find a generic handler like handle_SendShortMessage # try to find a generic handler like handle_SendShortMessage
handle_name = 'handle_%s' % type(parsed).__name__ handle_name = 'handle_%s' % type(parsed).__name__
if hasattr(self, handle_name): if hasattr(self, handle_name):
@@ -160,13 +157,57 @@ class LinkBase(abc.ABC):
sw : string (in hex) of status word (ex. "9000") sw : string (in hex) of status word (ex. "9000")
""" """
rv = self.send_apdu(pdu) rv = self.send_apdu(pdu)
last_sw = rv[1]
while sw == '9000' and sw_match(rv[1], '91xx'): while sw == '9000' and sw_match(last_sw, '91xx'):
# It *was* successful after all -- the extra pieces FETCH handled
# need not concern the caller.
rv = (rv[0], '9000')
# proactive sim as per TS 102 221 Setion 7.4.2 # proactive sim as per TS 102 221 Setion 7.4.2
rv = self.send_apdu_checksw('80120000' + rv[1][2:], sw) # TODO: Check SW manually to avoid recursing on the stack (provided this piece of code stays in this place)
print("FETCH: %s" % rv[0]) fetch_rv = self.send_apdu_checksw('80120000' + last_sw[2:], sw)
# Setting this in case we later decide not to send a terminal
# response immediately unconditionally -- the card may still have
# something pending even though the last command was not processed
# yet.
last_sw = fetch_rv[1]
# parse the proactive command
pcmd = ProactiveCommand()
parsed = pcmd.from_tlv(h2b(fetch_rv[0]))
print("FETCH: %s (%s)" % (fetch_rv[0], type(parsed).__name__))
result = Result()
if self.proactive_handler: if self.proactive_handler:
self.proactive_handler.receive_fetch_raw(rv[0]) # Extension point: If this does return a list of TLV objects,
# they could be appended after the Result; if the first is a
# Result, that cuold replace the one built here.
self.proactive_handler.receive_fetch_raw(pcmd, parsed)
result.from_dict({'general_result': 'performed_successfully', 'additional_information': ''})
else:
result.from_dict({'general_result': 'command_beyond_terminal_capability', 'additional_information': ''})
# Send response immediately, thus also flushing out any further
# proactive commands that the card already wants to send
#
# Structure as per TS 102 223 V4.4.0 Section 6.8
# The Command Details are echoed from the command that has been processed.
(command_details,) = [c for c in pcmd.decoded.children if isinstance(c, CommandDetails)]
# The Device Identities are fixed. (TS 102 223 V4.0.0 Section 6.8.2)
device_identities = DeviceIdentities()
device_identities.from_dict({'source_dev_id': 'terminal', 'dest_dev_id': 'uicc'})
# Testing hint: The value of tail does not influence the behavior
# of an SJA2 that sent ans SMS, so this is implemented only
# following TS 102 223, and not fully tested.
tail = command_details.to_tlv() + device_identities.to_tlv() + result.to_tlv()
# Testing hint: In contrast to the above, this part is positively
# essential to get the SJA2 to provide the later parts of a
# multipart SMS in response to an OTA RFM command.
terminal_response = '80140000' + b2h(len(tail).to_bytes(1, 'big') + tail)
terminal_response_rv = self.send_apdu(terminal_response)
last_sw = terminal_response_rv[1]
if not sw_match(rv[1], sw): if not sw_match(rv[1], sw):
raise SwMatchError(rv[1], sw.lower(), self.sw_interpreter) raise SwMatchError(rv[1], sw.lower(), self.sw_interpreter)
return rv return rv

View File

@@ -3,7 +3,7 @@ pyserial
pytlv pytlv
cmd2==1.5 cmd2==1.5
jsonpath-ng jsonpath-ng
construct construct>=2.9.51
bidict bidict
gsm0338 gsm0338
pyyaml>=5.1 pyyaml>=5.1

View File

@@ -14,7 +14,7 @@ setup(
"pytlv", "pytlv",
"cmd2 >= 1.3.0, < 2.0.0", "cmd2 >= 1.3.0, < 2.0.0",
"jsonpath-ng", "jsonpath-ng",
"construct >= 2.9", "construct >= 2.9.51",
"bidict", "bidict",
"gsm0338", "gsm0338",
"termcolor", "termcolor",