dexter has uploaded this change for review. ( https://gerrit.osmocom.org/c/pysim/+/38658?usp=email )
Change subject: pySim-shell_test: add new testcase for card initialization
......................................................................
pySim-shell_test: add new testcase for card initialization
The card initialization normally takes place automatically. Nearly all
testcases implicitly cover this code-path. However, it is also possible
to skip the card initialization and do it at some later point. This is
commonly the case for unprovisioned card that require some custom APDUs
in a basic initialization step. When this step is done one would use
the "equip" command to level up to the full featured mode. This patch
adds a testcase for this scenario
Related: OS#6367
Change-Id: I01a03fa07d8c62164453bd707c5943288ff1a972
---
A tests/pySim-shell_test/card_init/__init__.py
A tests/pySim-shell_test/card_init/test.py
A tests/pySim-shell_test/card_init/test_card_init.script
3 files changed, 62 insertions(+), 0 deletions(-)
git pull ssh://gerrit.osmocom.org:29418/pysim refs/changes/58/38658/1
diff --git a/tests/pySim-shell_test/card_init/__init__.py b/tests/pySim-shell_test/card_init/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tests/pySim-shell_test/card_init/__init__.py
diff --git a/tests/pySim-shell_test/card_init/test.py b/tests/pySim-shell_test/card_init/test.py
new file mode 100644
index 0000000..2f25057
--- /dev/null
+++ b/tests/pySim-shell_test/card_init/test.py
@@ -0,0 +1,33 @@
+# Testsuite for pySim-shell.py
+#
+# (C) 2024 by sysmocom - s.f.m.c. GmbH
+# All Rights Reserved
+#
+# Author: Philipp Maier
+#
+# 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/>.
+
+import unittest
+import os
+from utils import *
+
+class test_case(UnittestUtils):
+
+ def test_card_init(self):
+ cardname = 'sysmoISIM-SJA5-S17'
+
+ self.runPySimShell(cardname, "test_card_init.script", no_exceptions = True, skip_card_init = True)
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/tests/pySim-shell_test/card_init/test_card_init.script b/tests/pySim-shell_test/card_init/test_card_init.script
new file mode 100644
index 0000000..9f6d9b0
--- /dev/null
+++ b/tests/pySim-shell_test/card_init/test_card_init.script
@@ -0,0 +1,29 @@
+set debug true
+set echo true
+
+# In this test we start without initializing any card profile. In this situation
+# only the "apdu" command and the "reset" command will work.
+
+# Select a file deep in the file system using APDUs only
+apdu 00a40004023f0000 --expect-sw 9000 --expect-response-regex '^[a-fA-F0-9]+$'
+apdu 00a4040410a0000000871002ffffffff890709000000 --expect-sw 9000 --expect-response-regex '^[a-fA-F0-9]+$'
+apdu 00a40004026f0700 --expect-sw 9000 --expect-response-regex '^[a-fA-F0-9]+$'
+
+# Try a reset
+reset
+
+# If the reset had an effect, selecting a file at MF level should work
+apdu 00a40004022fe200 --expect-sw 9000 --expect-response-regex '^[a-fA-F0-9]+$'
+
+# Equip the card, this will do the profile initialization. After this,
+# pySim-shell commands should work normally.
+equip
+
+# Select a file deep in the file system
+select MF/ADF.USIM/EF.IMSI
+
+# Try again to reset the card
+reset
+
+# If the reset had an effect, selecting a file at MF level should work
+select EF.ICCID
--
To view, visit https://gerrit.osmocom.org/c/pysim/+/38658?usp=email
To unsubscribe, or for help writing mail filters, visit https://gerrit.osmocom.org/settings?usp=email
Gerrit-MessageType: newchange
Gerrit-Project: pysim
Gerrit-Branch: master
Gerrit-Change-Id: I01a03fa07d8c62164453bd707c5943288ff1a972
Gerrit-Change-Number: 38658
Gerrit-PatchSet: 1
Gerrit-Owner: dexter <pmaier(a)sysmocom.de>
dexter has uploaded this change for review. ( https://gerrit.osmocom.org/c/pysim/+/38657?usp=email )
Change subject: pySim/transport add support for T=1 protocol and fix APDU/TPDU layer conflicts
......................................................................
pySim/transport add support for T=1 protocol and fix APDU/TPDU layer conflicts
ETSI TS 102 221, section 7.3 specifies that UICCs (and eUICCs) may support two
different transport protocols: T=0 or T=1 or both. The spec also says that the
terminal must support both protocols.
This patch adds the necessary functionality to support the T=1 protocol
alongside the T=0 protocol. However, this also means that we have to sharpen
the lines between APDUs and TPDUs.
Change-Id: I8b56d7804a2b4c392f43f8540e0b6e70001a8970
---
M pySim-read.py
M pySim-shell.py
M pySim-trace.py
M pySim/commands.py
M pySim/euicc.py
M pySim/global_platform/__init__.py
M pySim/global_platform/scp.py
M pySim/transport/__init__.py
M pySim/transport/calypso.py
M pySim/transport/modem_atcmd.py
M pySim/transport/pcsc.py
M pySim/transport/serial.py
M pySim/utils.py
A tests/pySim-shell_test/apdu/__init__.py
A tests/pySim-shell_test/apdu/test.py
A tests/pySim-shell_test/apdu/test_apdu.script
A tests/pySim-shell_test/apdu/test_apdu_legacy.script
A tests/pySim-shell_test/apdu/test_apdu_legacy_scp02.template
A tests/pySim-shell_test/apdu/test_apdu_legacy_scp03.template
A tests/pySim-shell_test/apdu/test_apdu_scp02.template
A tests/pySim-shell_test/apdu/test_apdu_scp03.template
M tests/pySim-shell_test/config.yaml
M tests/pySim-shell_test/lchan/test.ok
M tests/unittests/test_globalplatform.py
M tests/unittests/test_utils.py
25 files changed, 604 insertions(+), 144 deletions(-)
git pull ssh://gerrit.osmocom.org:29418/pysim refs/changes/57/38657/1
diff --git a/pySim-read.py b/pySim-read.py
index 4ede6dd..91e17ae 100755
--- a/pySim-read.py
+++ b/pySim-read.py
@@ -88,7 +88,7 @@
scc.sel_ctrl = "0004"
# Testing for Classic SIM or UICC
- (res, sw) = sl.send_apdu(scc.cla_byte + "a4" + scc.sel_ctrl + "02" + "3f00")
+ (res, sw) = sl.send_apdu(scc.cla_byte + "a4" + scc.sel_ctrl + "02" + "3f00" + "00")
if sw == '6e00':
# Just a Classic SIM
scc.cla_byte = "a0"
diff --git a/pySim-shell.py b/pySim-shell.py
index b2de874..08f00f9 100755
--- a/pySim-shell.py
+++ b/pySim-shell.py
@@ -114,6 +114,7 @@
self.conserve_write = True
self.json_pretty_print = True
self.apdu_trace = False
+ self.apdu_strict = False
self.add_settable(Settable2Compat('numeric_path', bool, 'Print File IDs instead of names', self,
onchange_cb=self._onchange_numeric_path))
@@ -122,6 +123,9 @@
self.add_settable(Settable2Compat('json_pretty_print', bool, 'Pretty-Print JSON output', self))
self.add_settable(Settable2Compat('apdu_trace', bool, 'Trace and display APDUs exchanged with card', self,
onchange_cb=self._onchange_apdu_trace))
+ self.add_settable(Settable2Compat('apdu_strict', bool,
+ 'Enforce APDU responses according to ISO/IEC 7816-3, table 12', self,
+ onchange_cb=self._onchange_apdu_strict))
self.equip(card, rs)
def equip(self, card, rs):
@@ -198,6 +202,13 @@
else:
self.card._scc._tp.apdu_tracer = None
+ def _onchange_apdu_strict(self, param_name, old, new):
+ if self.card:
+ if new == True:
+ self.card._scc._tp.apdu_strict = True
+ else:
+ self.card._scc._tp.apdu_strict = False
+
class Cmd2ApduTracer(ApduTracer):
def __init__(self, cmd2_app):
self.cmd2 = cmd2_app
diff --git a/pySim-trace.py b/pySim-trace.py
index 46d4c40..a45fcfc 100755
--- a/pySim-trace.py
+++ b/pySim-trace.py
@@ -55,7 +55,7 @@
def __str__(self):
return "dummy"
- def _send_apdu_raw(self, pdu):
+ def _send_apdu(self, pdu):
#print("DummySimLink-apdu: %s" % pdu)
return [], '9000'
diff --git a/pySim/commands.py b/pySim/commands.py
index 0173f70..af9f247 100644
--- a/pySim/commands.py
+++ b/pySim/commands.py
@@ -142,8 +142,9 @@
Tuple of (decoded_data, sw)
"""
cmd = cmd_constr.build(cmd_data) if cmd_data else ''
- p3 = i2h([len(cmd)])
- pdu = ''.join([cla, ins, p1, p2, p3, b2h(cmd)])
+ lc = i2h([len(cmd)]) if cmd_data else ''
+ le = '00' if resp_constr else ''
+ pdu = ''.join([cla, ins, p1, p2, lc, b2h(cmd), le])
(data, sw) = self.send_apdu(pdu, apply_lchan = apply_lchan)
if data:
# filter the resulting dict to avoid '_io' members inside
@@ -247,7 +248,7 @@
if not isinstance(dir_list, list):
dir_list = [dir_list]
for i in dir_list:
- data, sw = self.send_apdu(self.cla_byte + "a4" + self.sel_ctrl + "02" + i)
+ data, sw = self.send_apdu(self.cla_byte + "a4" + self.sel_ctrl + "02" + i + "00")
rv.append((data, sw))
if sw != '9000':
return rv
@@ -277,11 +278,11 @@
fid : file identifier as hex string
"""
- return self.send_apdu_checksw(self.cla_byte + "a4" + self.sel_ctrl + "02" + fid)
+ return self.send_apdu_checksw(self.cla_byte + "a4" + self.sel_ctrl + "02" + fid + "00")
def select_parent_df(self) -> ResTuple:
"""Execute SELECT to switch to the parent DF """
- return self.send_apdu_checksw(self.cla_byte + "a4030400")
+ return self.send_apdu_checksw(self.cla_byte + "a40304")
def select_adf(self, aid: Hexstr) -> ResTuple:
"""Execute SELECT a given Applicaiton ADF.
@@ -291,7 +292,7 @@
"""
aidlen = ("0" + format(len(aid) // 2, 'x'))[-2:]
- return self.send_apdu_checksw(self.cla_byte + "a4" + "0404" + aidlen + aid)
+ return self.send_apdu_checksw(self.cla_byte + "a4" + "0404" + aidlen + aid + "00")
def read_binary(self, ef: Path, length: int = None, offset: int = 0) -> ResTuple:
"""Execute READD BINARY.
@@ -494,9 +495,9 @@
# TS 102 221 Section 11.3.1 low-level helper
def _retrieve_data(self, tag: int, first: bool = True) -> ResTuple:
if first:
- pdu = '80cb008001%02x' % (tag)
+ pdu = '80cb008001%02x00' % (tag)
else:
- pdu = '80cb000000'
+ pdu = '80cb0000'
return self.send_apdu_checksw(pdu)
def retrieve_data(self, ef: Path, tag: int) -> ResTuple:
@@ -569,7 +570,7 @@
if len(rand) != 32:
raise ValueError('Invalid rand')
self.select_path(['3f00', '7f20'])
- return self.send_apdu_checksw('a088000010' + rand, sw='9000')
+ return self.send_apdu_checksw('a088000010' + rand + '00', sw='9000')
def authenticate(self, rand: Hexstr, autn: Hexstr, context: str = '3g') -> ResTuple:
"""Execute AUTHENTICATE (USIM/ISIM).
@@ -602,7 +603,7 @@
def status(self) -> ResTuple:
"""Execute a STATUS command as per TS 102 221 Section 11.1.2."""
- return self.send_apdu_checksw('80F2000000')
+ return self.send_apdu_checksw('80F20000')
def deactivate_file(self) -> ResTuple:
"""Execute DECATIVATE FILE command as per TS 102 221 Section 11.1.14."""
@@ -651,7 +652,7 @@
p1 = 0x80
else:
p1 = 0x00
- pdu = self.cla_byte + '70%02x%02x00' % (p1, lchan_nr)
+ pdu = self.cla_byte + '70%02x%02x' % (p1, lchan_nr)
return self.send_apdu_checksw(pdu)
def reset_card(self) -> Hexstr:
diff --git a/pySim/euicc.py b/pySim/euicc.py
index 069a5c7..e9321d6 100644
--- a/pySim/euicc.py
+++ b/pySim/euicc.py
@@ -332,7 +332,7 @@
def store_data(scc: SimCardCommands, tx_do: Hexstr, exp_sw: SwMatchstr ="9000") -> Tuple[Hexstr, SwHexstr]:
"""Perform STORE DATA according to Table 47+48 in Section 5.7.2 of SGP.22.
Only single-block store supported for now."""
- capdu = '80E29100%02x%s' % (len(tx_do)//2, tx_do)
+ capdu = '80E29100%02x%s00' % (len(tx_do)//2, tx_do)
return scc.send_apdu_checksw(capdu, exp_sw)
@staticmethod
diff --git a/pySim/global_platform/__init__.py b/pySim/global_platform/__init__.py
index 53e13e8..fddab4e 100644
--- a/pySim/global_platform/__init__.py
+++ b/pySim/global_platform/__init__.py
@@ -580,7 +580,7 @@
{'last_block': len(remainder) == 0, 'encryption': encryption,
'structure': structure, 'response': response_permitted})
hdr = "80E2%02x%02x%02x" % (p1b[0], block_nr, len(chunk))
- data, _sw = self._cmd.lchan.scc.send_apdu_checksw(hdr + b2h(chunk))
+ data, _sw = self._cmd.lchan.scc.send_apdu_checksw(hdr + b2h(chunk) + "00")
block_nr += 1
response += data
return data
@@ -646,7 +646,7 @@
See GlobalPlatform CardSpecification v2.3 Section 11.8 for details."""
key_data = kvn.to_bytes(1, 'big') + build_construct(ADF_SD.AddlShellCommands.KeyDataBasic, key_dict)
hdr = "80D8%02x%02x%02x" % (old_kvn, kid, len(key_data))
- data, _sw = self._cmd.lchan.scc.send_apdu_checksw(hdr + b2h(key_data))
+ data, _sw = self._cmd.lchan.scc.send_apdu_checksw(hdr + b2h(key_data) + "00")
return data
get_status_parser = argparse.ArgumentParser()
@@ -671,7 +671,7 @@
grd_list = []
while True:
hdr = "80F2%s%02x%02x" % (subset_hex, p2, len(cmd_data))
- data, sw = self._cmd.lchan.scc.send_apdu(hdr + b2h(cmd_data))
+ data, sw = self._cmd.lchan.scc.send_apdu(hdr + b2h(cmd_data) + "00")
remainder = h2b(data)
while len(remainder):
# tlv sequence, each element is one GpRegistryRelatedData()
@@ -752,7 +752,7 @@
self.install(p1, 0x00, b2h(ifi_bytes))
def install(self, p1:int, p2:int, data:Hexstr) -> ResTuple:
- cmd_hex = "80E6%02x%02x%02x%s" % (p1, p2, len(data)//2, data)
+ cmd_hex = "80E6%02x%02x%02x%s00" % (p1, p2, len(data)//2, data)
return self._cmd.lchan.scc.send_apdu_checksw(cmd_hex)
del_cc_parser = argparse.ArgumentParser()
diff --git a/pySim/global_platform/scp.py b/pySim/global_platform/scp.py
index ebdd880..ead4c26 100644
--- a/pySim/global_platform/scp.py
+++ b/pySim/global_platform/scp.py
@@ -24,7 +24,7 @@
from construct import Optional as COptional
from osmocom.utils import b2h
from osmocom.tlv import bertlv_parse_len, bertlv_encode_len
-
+from pySim.utils import parse_command_apdu
from pySim.secure_channel import SecureChannel
logger = logging.getLogger(__name__)
@@ -248,7 +248,7 @@
def gen_init_update_apdu(self, host_challenge: bytes = b'\x00'*8) -> bytes:
"""Generate INITIALIZE UPDATE APDU."""
self.host_challenge = host_challenge
- return bytes([self._cla(), INS_INIT_UPDATE, self.card_keys.kvn, 0, 8]) + self.host_challenge
+ return bytes([self._cla(), INS_INIT_UPDATE, self.card_keys.kvn, 0, 8]) + self.host_challenge + b'\x00'
def parse_init_update_resp(self, resp_bin: bytes):
"""Parse response to INITIALZIE UPDATE."""
@@ -277,9 +277,13 @@
"""Wrap Command APDU for SCP02: calculate MAC and encrypt."""
logger.debug("wrap_cmd_apdu(%s)", b2h(apdu))
if self.do_cmac:
- lc = len(apdu) - 5
- assert len(apdu) >= 5, "Wrong APDU length: %d" % len(apdu)
- assert len(apdu) == 5 or apdu[4] == lc, "Lc differs from length of data: %d vs %d" % (apdu[4], lc)
+ (case, lc, le, data) = parse_command_apdu(apdu)
+
+ # TODO: add support for extended length fields.
+ assert lc <= 256
+ assert le <= 256
+ lc &= 0xFF
+ le &= 0xFF
# CLA without log. channel can be 80 or 00 only
cla = apdu[0]
@@ -295,17 +299,25 @@
# CMAC on modified APDU
mlc = lc + 8
clac = cla | CLA_SM
- mac = self.sk.calc_mac_1des(bytes([clac]) + apdu[1:4] + bytes([mlc]) + apdu[5:])
+ mac = self.sk.calc_mac_1des(bytes([clac]) + apdu[1:4] + bytes([mlc]) + data)
if self.do_cenc:
k = DES3.new(self.sk.enc, DES.MODE_CBC, b'\x00'*8)
- data = k.encrypt(pad80(apdu[5:], 8))
+ data = k.encrypt(pad80(data, 8))
lc = len(data)
- else:
- data = apdu[5:]
lc += 8
apdu = bytes([self._cla(True, b8)]) + apdu[1:4] + bytes([lc]) + data + mac
+ # Since we attach a signature, we will always send some data. This means that if the APDU is of case #4
+ # or case #2, we must attach an additional Le byte to signal that we expect a response. It is technically
+ # legal to use 0x00 (=256) as Le byte, even when the caller has specified a different value in the original
+ # APDU. This is due to the fact that Le always describes the maximum expected length of the response
+ # (see also ISO/IEC 7816-4, section 5.1). In addition to that, it should also important that depending on
+ # the configuration of the SCP, the response may also contain a signature that makes the response larger
+ # than specified in the Le field of the original APDU.
+ if case == 4 or case == 2:
+ apdu += b'\x00'
+
return apdu
def unwrap_rsp_apdu(self, sw: bytes, rsp_apdu: bytes) -> bytes:
@@ -452,7 +464,7 @@
if len(host_challenge) != self.s_mode:
raise ValueError('Host Challenge must be %u bytes long' % self.s_mode)
self.host_challenge = host_challenge
- return bytes([self._cla(), INS_INIT_UPDATE, self.card_keys.kvn, 0, len(host_challenge)]) + host_challenge
+ return bytes([self._cla(), INS_INIT_UPDATE, self.card_keys.kvn, 0, len(host_challenge)]) + host_challenge + b'\x00'
def parse_init_update_resp(self, resp_bin: bytes):
"""Parse response to INITIALIZE UPDATE."""
@@ -484,12 +496,16 @@
ins = apdu[1]
p1 = apdu[2]
p2 = apdu[3]
- lc = apdu[4]
- assert lc == len(apdu) - 5
- cmd_data = apdu[5:]
+ (case, lc, le, cmd_data) = parse_command_apdu(apdu)
+
+ # TODO: add support for extended length fields.
+ assert lc <= 256
+ assert le <= 256
+ lc &= 0xFF
+ le &= 0xFF
if self.do_cenc and not skip_cenc:
- if lc == 0:
+ if case <= 2:
# No encryption shall be applied to a command where there is no command data field. In this
# case, the encryption counter shall still be incremented
self.sk.block_nr += 1
@@ -515,6 +531,10 @@
cmac = self.sk.calc_cmac(apdu)
apdu += cmac[:self.s_mode]
+ # See comment in SCP03._wrap_cmd_apdu()
+ if case == 4 or case == 2:
+ apdu += b'\x00'
+
return apdu
def unwrap_rsp_apdu(self, sw: bytes, rsp_apdu: bytes) -> bytes:
diff --git a/pySim/transport/__init__.py b/pySim/transport/__init__.py
index 9482528..5743978 100644
--- a/pySim/transport/__init__.py
+++ b/pySim/transport/__init__.py
@@ -11,7 +11,7 @@
from osmocom.utils import b2h, h2b, i2h, Hexstr
from pySim.exceptions import *
-from pySim.utils import SwHexstr, SwMatchstr, ResTuple, sw_match
+from pySim.utils import SwHexstr, SwMatchstr, ResTuple, sw_match, parse_command_apdu
from pySim.cat import ProactiveCommand, CommandDetails, DeviceIdentities, Result
#
@@ -90,14 +90,16 @@
self.sw_interpreter = sw_interpreter
self.apdu_tracer = apdu_tracer
self.proactive_handler = proactive_handler
+ self.apdu_strict = False
@abc.abstractmethod
def __str__(self) -> str:
"""Implementation specific method for printing an information to identify the device."""
@abc.abstractmethod
- def _send_apdu_raw(self, pdu: Hexstr) -> ResTuple:
- """Implementation specific method for sending the PDU."""
+ def _send_apdu(self, apdu: Hexstr) -> ResTuple:
+ """Implementation specific method for sending the APDU. This method must accept APDUs as defined in
+ ISO/IEC 7816-3, section 12.1 """
def set_sw_interpreter(self, interp):
"""Set an (optional) status word interpreter."""
@@ -134,61 +136,51 @@
self.apdu_tracer.trace_reset()
return self._reset_card()
- def send_apdu_raw(self, pdu: Hexstr) -> ResTuple:
+ def send_apdu(self, apdu: Hexstr) -> ResTuple:
"""Sends an APDU with minimal processing
Args:
- pdu : string of hexadecimal characters (ex. "A0A40000023F00")
+ apdu : string of hexadecimal characters (ex. "A0A40000023F00", must comply to ISO/IEC 7816-3, section 12.1)
Returns:
tuple(data, sw), where
data : string (in hex) of returned data (ex. "074F4EFFFF")
sw : string (in hex) of status word (ex. "9000")
"""
+
+ # To make sure that no invalid APDUs can be passed further down into the transport layer, we parse the APDU.
+ (case, _lc, _le, _data) = parse_command_apdu(h2b(apdu))
+
if self.apdu_tracer:
- self.apdu_tracer.trace_command(pdu)
- (data, sw) = self._send_apdu_raw(pdu)
+ self.apdu_tracer.trace_command(apdu)
+
+ # Handover APDU to concrete transport layer implementation
+ (data, sw) = self._send_apdu(apdu)
+
if self.apdu_tracer:
- self.apdu_tracer.trace_response(pdu, sw, data)
+ self.apdu_tracer.trace_response(apdu, sw, data)
+
+ # The APDU case (See aso ISO/IEC 7816-3, table 12) dictates if we should receive a response or not. If we
+ # receive a response in an APDU case that does not allow the reception of a respnse we print a warning to
+ # make the user/caller aware of the problem. Since the transaction is over at this point and data was received
+ # we count it as a successful transaction anyway, even though the spec was violated. The problem is most likely
+ # caused by a missing Le field in the APDU. This is an error that the caller/user should correct to avoid
+ # problems at some later point when a different transport protocol or transport layer implementation is used.
+ # All APDUs passed to this function must comply to ISO/IEC 7816-3, section 12.
+ if len(data) > 0 and (case == 3 or case == 1):
+ exeption_str = 'received unexpected response data, incorrect APDU-case ' + \
+ '(%d, should be %d, missing Le field?)!' % (case, case + 1)
+ if self.apdu_strict:
+ raise ValueError(exeption_str)
+ else:
+ print('Warning: %s' % exeption_str)
+
return (data, sw)
- def send_apdu(self, pdu: Hexstr) -> ResTuple:
- """Sends an APDU and auto fetch response data
-
- Args:
- pdu : string of hexadecimal characters (ex. "A0A40000023F00")
- Returns:
- tuple(data, sw), where
- data : string (in hex) of returned data (ex. "074F4EFFFF")
- sw : string (in hex) of status word (ex. "9000")
- """
- prev_pdu = pdu
- data, sw = self.send_apdu_raw(pdu)
-
- # When we have sent the first APDU, the SW may indicate that there are response bytes
- # available. There are two SWs commonly used for this 9fxx (sim) and 61xx (usim), where
- # xx is the number of response bytes available.
- # See also:
- if sw is not None:
- while (sw[0:2] in ['9f', '61', '62', '63']):
- # SW1=9F: 3GPP TS 51.011 9.4.1, Responses to commands which are correctly executed
- # SW1=61: ISO/IEC 7816-4, Table 5 — General meaning of the interindustry values of SW1-SW2
- # SW1=62: ETSI TS 102 221 7.3.1.1.4 Clause 4b): 62xx, 63xx, 9xxx != 9000
- pdu_gr = pdu[0:2] + 'c00000' + sw[2:4]
- prev_pdu = pdu_gr
- d, sw = self.send_apdu_raw(pdu_gr)
- data += d
- if sw[0:2] == '6c':
- # SW1=6C: ETSI TS 102 221 Table 7.1: Procedure byte coding
- pdu_gr = prev_pdu[0:8] + sw[2:4]
- data, sw = self.send_apdu_raw(pdu_gr)
-
- return data, sw
-
- def send_apdu_checksw(self, pdu: Hexstr, sw: SwMatchstr = "9000") -> ResTuple:
+ def send_apdu_checksw(self, apdu: Hexstr, sw: SwMatchstr = "9000") -> ResTuple:
"""Sends an APDU and check returned SW
Args:
- pdu : string of hexadecimal characters (ex. "A0A40000023F00")
+ apdu : string of hexadecimal characters (ex. "A0A40000023F00", must comply to ISO/IEC 7816-3, section 12.1)
sw : string of 4 hexadecimal characters (ex. "9000"). The user may mask out certain
digits using a '?' to add some ambiguity if needed.
Returns:
@@ -196,7 +188,7 @@
data : string (in hex) of returned data (ex. "074F4EFFFF")
sw : string (in hex) of status word (ex. "9000")
"""
- rv = self.send_apdu(pdu)
+ rv = self.send_apdu(apdu)
last_sw = rv[1]
while sw == '9000' and sw_match(last_sw, '91xx'):
@@ -247,6 +239,89 @@
raise SwMatchError(rv[1], sw.lower(), self.sw_interpreter)
return rv
+
+class TpduAdaptor():
+
+ # Use the T=0 TPDU format by default as this is the most commonly used transport protocol.
+ protocol = 0
+
+ def set_tpdu_format(self, protocol: int):
+ """Set TPDU format. Each transport protocol has its specific TPDU format. This method allows the
+ concrete transport layer implementation to set the TPDU format it expects. (This method must not be
+ called by higher layers. Switching the TPDU format does not switch the transport protocol that the
+ reader uses on the wire)
+
+ Args:
+ protocol : number of the transport protocol used. (0 => T=0, 1 => T=1)
+ """
+ self.protocol = protocol
+
+ @abc.abstractmethod
+ def send_tpdu(self, tpdu: Hexstr) -> ResTuple:
+ """Implementation specific method for sending the resulting TPDU. This method must accept TPDUs as defined in
+ ETSI TS 102 221, section 7.3.1 and 7.3.2, depending on the protocol selected. """
+
+ def _send_apdu(self, apdu: Hexstr) -> ResTuple:
+ """Transforms APDU into a TPDU and sends it. The response TPDU is returned as APDU back to the caller.
+
+ Args:
+ apdu : string of hexadecimal characters (eg. "A0A40000023F00", must comply to ISO/IEC 7816-3, section 12)
+ Returns:
+ tuple(data, sw), where
+ data : string (in hex) of returned data (ex. "074F4EFFFF")
+ sw : string (in hex) of status word (ex. "9000")
+ """
+
+ if self.protocol == 0:
+ return self.__send_apdu_T0(apdu)
+ elif self.protocol == 1:
+ return self.__send_apdu_T1(apdu)
+ raise ValueError('unspported protocol selected (T=%d)' % self.protocol)
+
+ def __send_apdu_T0(self, apdu: Hexstr) -> ResTuple:
+ # Transform the given APDU to the T=0 TPDU format and send it. Automatically fetch the response (case #4 APDUs)
+ # (see also ETSI TS 102 221, section 7.3.1.1)
+
+ # Transform APDU to T=0 TPDU (see also ETSI TS 102 221, section 7.3.1)
+ (case, _lc, _le, _data) = parse_command_apdu(h2b(apdu))
+
+ if case == 1:
+ # Attach an Le field to all case #1 APDUs (see also ETSI TS 102 221, section 7.3.1.1.1)
+ tpdu = apdu + '00'
+ elif case == 4:
+ # Remove the Le field from all case #4 APDUs (see also ETSI TS 102 221, section 7.3.1.1.4)
+ tpdu = apdu[:-2]
+ else:
+ tpdu = apdu
+
+ prev_tpdu = tpdu
+ data, sw = self.send_tpdu(tpdu)
+
+ # When we have sent the first APDU, the SW may indicate that there are response bytes
+ # available. There are two SWs commonly used for this 9fxx (sim) and 61xx (usim), where
+ # xx is the number of response bytes available.
+ # See also:
+ if sw is not None:
+ while (sw[0:2] in ['9f', '61', '62', '63']):
+ # SW1=9F: 3GPP TS 51.011 9.4.1, Responses to commands which are correctly executed
+ # SW1=61: ISO/IEC 7816-4, Table 5 — General meaning of the interindustry values of SW1-SW2
+ # SW1=62: ETSI TS 102 221 7.3.1.1.4 Clause 4b): 62xx, 63xx, 9xxx != 9000
+ tpdu_gr = tpdu[0:2] + 'c00000' + sw[2:4]
+ prev_tpdu = tpdu_gr
+ d, sw = self.send_tpdu(tpdu_gr)
+ data += d
+ if sw[0:2] == '6c':
+ # SW1=6C: ETSI TS 102 221 Table 7.1: Procedure byte coding
+ tpdu_gr = prev_tpdu[0:8] + sw[2:4]
+ data, sw = self.send_tpdu(tpdu_gr)
+
+ return data, sw
+
+ def __send_apdu_T1(self, apdu: Hexstr) -> ResTuple:
+ # The T=1 TPDU format is the same as the APDU format, so we may pass the given APDU through without modification
+ # (see also ETSI TS 102 221, section 7.3.2.0)
+ return self.send_tpdu(apdu)
+
def argparse_add_reader_args(arg_parser: argparse.ArgumentParser):
"""Add all reader related arguments to the given argparse.Argumentparser instance."""
from pySim.transport.serial import SerialSimLink
diff --git a/pySim/transport/calypso.py b/pySim/transport/calypso.py
index 9462939..0e2b7b9 100644
--- a/pySim/transport/calypso.py
+++ b/pySim/transport/calypso.py
@@ -24,7 +24,7 @@
from typing import Optional
from osmocom.utils import h2b, b2h, Hexstr
-from pySim.transport import LinkBase
+from pySim.transport import LinkBase, TpduAdaptor
from pySim.exceptions import ReaderError, ProtocolError
from pySim.utils import ResTuple
@@ -70,12 +70,12 @@
L1CTL_SIM_REQ = 0x16
L1CTL_SIM_CONF = 0x17
- def __init__(self, pdu):
+ def __init__(self, tpdu):
super().__init__(self.L1CTL_SIM_REQ)
- self.data += pdu
+ self.data += tpdu
-class CalypsoSimLink(LinkBase):
+class CalypsoSimLink(TpduAdaptor, LinkBase):
"""Transport Link for Calypso based phones."""
name = 'Calypso-based (OsmocomBB) reader'
@@ -129,10 +129,10 @@
def wait_for_card(self, timeout: Optional[int] = None, newcardonly: bool = False):
pass # Nothing to do really ...
- def _send_apdu_raw(self, pdu: Hexstr) -> ResTuple:
+ def send_tpdu(self, tpdu: Hexstr) -> ResTuple:
- # Request FULL reset
- req_msg = L1CTLMessageSIM(h2b(pdu))
+ # Request sending of TPDU
+ req_msg = L1CTLMessageSIM(h2b(tpdu))
self.sock.send(req_msg.gen_msg())
# Read message length first
diff --git a/pySim/transport/modem_atcmd.py b/pySim/transport/modem_atcmd.py
index 8970f7d..21879a3 100644
--- a/pySim/transport/modem_atcmd.py
+++ b/pySim/transport/modem_atcmd.py
@@ -25,14 +25,14 @@
from osmocom.utils import Hexstr
from pySim.utils import ResTuple
-from pySim.transport import LinkBase
+from pySim.transport import LinkBase, TpduAdaptor
from pySim.exceptions import ReaderError, ProtocolError
# HACK: if somebody needs to debug this thing
# log.root.setLevel(log.DEBUG)
-class ModemATCommandLink(LinkBase):
+class ModemATCommandLink(TpduAdaptor, LinkBase):
"""Transport Link for 3GPP TS 27.007 compliant modems."""
name = "modem for Generic SIM Access (3GPP TS 27.007)"
@@ -145,12 +145,12 @@
def wait_for_card(self, timeout: Optional[int] = None, newcardonly: bool = False):
pass # Nothing to do really ...
- def _send_apdu_raw(self, pdu: Hexstr) -> ResTuple:
+ def send_tpdu(self, tpdu: Hexstr) -> ResTuple:
# Make sure pdu has upper case hex digits [A-F]
- pdu = pdu.upper()
+ tpdu = tpdu.upper()
# Prepare the command as described in 8.17
- cmd = 'AT+CSIM=%d,\"%s\"' % (len(pdu), pdu)
+ cmd = 'AT+CSIM=%d,\"%s\"' % (len(tpdu), tpdu)
log.debug('Sending command: %s', cmd)
# Send AT+CSIM command to the modem
@@ -164,14 +164,14 @@
# Make sure that the response has format: b'+CSIM: %d,\"%s\"'
try:
result = re.match(b'\+CSIM: (\d+),\"([0-9A-F]+)\"', rsp)
- (_rsp_pdu_len, rsp_pdu) = result.groups()
+ (_rsp_tpdu_len, rsp_tpdu) = result.groups()
except Exception as exc:
raise ReaderError('Failed to parse response from modem: %s' % rsp) from exc
# TODO: make sure we have at least SW
- data = rsp_pdu[:-4].decode().lower()
- sw = rsp_pdu[-4:].decode().lower()
- log.debug('Command response: %s, %s', data, sw)
+ data = rsp_tpdu[:-4].decode().lower()
+ sw = rsp_tpdu[-4:].decode().lower()
+ log.debug('Command response: %s, %s', data, sw)
return data, sw
def __str__(self) -> str:
diff --git a/pySim/transport/pcsc.py b/pySim/transport/pcsc.py
index 207cf6c..3ad9919 100644
--- a/pySim/transport/pcsc.py
+++ b/pySim/transport/pcsc.py
@@ -30,11 +30,11 @@
from osmocom.utils import h2i, i2h, Hexstr
from pySim.exceptions import NoCardError, ProtocolError, ReaderError
-from pySim.transport import LinkBase
+from pySim.transport import LinkBase, TpduAdaptor
from pySim.utils import ResTuple
-class PcscSimLink(LinkBase):
+class PcscSimLink(TpduAdaptor, LinkBase):
""" pySim: PCSC reader transport link."""
name = 'PC/SC'
@@ -84,8 +84,19 @@
# is disconnected
self.disconnect()
- # Explicitly select T=0 communication protocol
- self._con.connect(CardConnection.T0_protocol)
+ # Make card connection and select a suitable communication protocol
+ self._con.connect()
+ supported_protocols = self._con.getProtocol();
+ self.disconnect()
+ if (supported_protocols & CardConnection.T0_protocol):
+ protocol = CardConnection.T0_protocol
+ self.set_tpdu_format(0)
+ elif (supported_protocols & CardConnection.T1_protocol):
+ protocol = CardConnection.T1_protocol
+ self.set_tpdu_format(1)
+ else:
+ raise ReaderError('Unsupported card protocol')
+ self._con.connect(protocol)
except CardConnectionException as exc:
raise ProtocolError() from exc
except NoCardException as exc:
@@ -102,12 +113,8 @@
self.connect()
return 1
- def _send_apdu_raw(self, pdu: Hexstr) -> ResTuple:
-
- apdu = h2i(pdu)
-
- data, sw1, sw2 = self._con.transmit(apdu)
-
+ def send_tpdu(self, tpdu: Hexstr) -> ResTuple:
+ data, sw1, sw2 = self._con.transmit(h2i(tpdu))
sw = [sw1, sw2]
# Return value
diff --git a/pySim/transport/serial.py b/pySim/transport/serial.py
index 04b4ab7..7b58774 100644
--- a/pySim/transport/serial.py
+++ b/pySim/transport/serial.py
@@ -24,11 +24,11 @@
from osmocom.utils import h2b, b2h, Hexstr
from pySim.exceptions import NoCardError, ProtocolError
-from pySim.transport import LinkBase
+from pySim.transport import LinkBase, TpduAdaptor
from pySim.utils import ResTuple
-class SerialSimLink(LinkBase):
+class SerialSimLink(TpduAdaptor, LinkBase):
""" pySim: Transport Link for serial (RS232) based readers included with simcard"""
name = 'Serial'
@@ -187,13 +187,13 @@
def _rx_byte(self):
return self._sl.read()
- def _send_apdu_raw(self, pdu: Hexstr) -> ResTuple:
+ def send_tpdu(self, tpdu: Hexstr) -> ResTuple:
- pdu = h2b(pdu)
- data_len = pdu[4] # P3
+ tpdu = h2b(tpdu)
+ data_len = tpdu[4] # P3
# Send first CLASS,INS,P1,P2,P3
- self._tx_string(pdu[0:5])
+ self._tx_string(tpdu[0:5])
# Wait ack which can be
# - INS: Command acked -> go ahead
@@ -201,7 +201,7 @@
# - SW1: The card can apparently proceed ...
while True:
b = self._rx_byte()
- if ord(b) == pdu[1]:
+ if ord(b) == tpdu[1]:
break
if b != '\x60':
# Ok, it 'could' be SW1
@@ -214,12 +214,12 @@
raise ProtocolError()
# Send data (if any)
- if len(pdu) > 5:
- self._tx_string(pdu[5:])
+ if len(tpdu) > 5:
+ self._tx_string(tpdu[5:])
# Receive data (including SW !)
- # length = [P3 - tx_data (=len(pdu)-len(hdr)) + 2 (SW1//2) ]
- to_recv = data_len - len(pdu) + 5 + 2
+ # length = [P3 - tx_data (=len(tpdu)-len(hdr)) + 2 (SW1//2) ]
+ to_recv = data_len - len(tpdu) + 5 + 2
data = bytes(0)
while len(data) < to_recv:
diff --git a/pySim/utils.py b/pySim/utils.py
index c706b34..48a9998 100644
--- a/pySim/utils.py
+++ b/pySim/utils.py
@@ -539,6 +539,52 @@
return res
+def parse_command_apdu(apdu: bytes) -> int:
+ """Parse a given command APDU and return case (see also ISO/IEC 7816-3, Table 12 and Figure 26),
+ lc, le and the data field.
+
+ Args:
+ apdu : hexstring that contains the command APDU
+ Returns:
+ tuple containing case, lc and le values of the APDU (case, lc, le, data)
+ """
+
+ if len(apdu) == 4:
+ # Case #1, No command data field, no response data field
+ lc = 0
+ le = 0
+ data = b''
+ return (1, lc, le, data)
+ elif len(apdu) == 5:
+ # Case #2, No command data field, response data field present
+ lc = 0
+ le = apdu[4]
+ if le == 0:
+ le = 256
+ data = b''
+ return (2, lc, le, data)
+ elif len(apdu) > 5:
+ lc = apdu[4];
+ if lc == 0:
+ lc = 256
+ data = apdu[5:lc+5]
+ if len(apdu) == 5 + lc:
+ # Case #3, Command data field present, no response data field
+ le = 0
+ return (3, lc, le, data)
+ elif len(apdu) == 5 + lc + 1:
+ # Case #4, Command data field present, no response data field
+ le = apdu[5 + lc]
+ if le == 0:
+ le = 256
+ return (4, lc, le, data)
+ else:
+ raise ValueError('invalid APDU (%s), Lc=0x%02x (%d) does not match the length (%d) of the data field'
+ % (b2h(apdu), lc, lc, len(apdu[5:])))
+ else:
+ raise ValueError('invalid APDU (%s), too short!' % b2h(apdu))
+
+
class DataObject(abc.ABC):
"""A DataObject (DO) in the sense of ISO 7816-4. Contrary to 'normal' TLVs where one
simply has any number of different TLVs that may occur in any order at any point, ISO 7816
diff --git a/tests/pySim-shell_test/apdu/__init__.py b/tests/pySim-shell_test/apdu/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tests/pySim-shell_test/apdu/__init__.py
diff --git a/tests/pySim-shell_test/apdu/test.py b/tests/pySim-shell_test/apdu/test.py
new file mode 100644
index 0000000..de79ae3
--- /dev/null
+++ b/tests/pySim-shell_test/apdu/test.py
@@ -0,0 +1,70 @@
+# Testsuite for pySim-shell.py
+#
+# (C) 2024 by sysmocom - s.f.m.c. GmbH
+# All Rights Reserved
+#
+# Author: Philipp Maier
+#
+# 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/>.
+
+import unittest
+import os
+from utils import *
+
+class test_case(UnittestUtils):
+
+ def test_apdu_legacy(self):
+ cardname = 'sysmoISIM-SJA5-S17'
+
+ self.runPySimShell(cardname, "test_apdu_legacy.script", no_exceptions = True)
+
+ def test_apdu_legacy_scp02(self):
+ cardname = 'sysmoISIM-SJA5-S17'
+
+ self.equipTemplate("test_apdu_legacy_scp02.script", SEC_LEVEL = 3)
+ self.runPySimShell(cardname, "test_apdu_legacy_scp02.script", no_exceptions = True, add_csv = True)
+ self.equipTemplate("test_apdu_legacy_scp02.script", SEC_LEVEL = 1)
+ self.runPySimShell(cardname, "test_apdu_legacy_scp02.script", no_exceptions = True, add_csv = True)
+
+ def test_apdu_legacy_scp03(self):
+ cardname = 'sysmoEUICC1-C2T'
+
+ self.equipTemplate("test_apdu_legacy_scp03.script", SEC_LEVEL = 3)
+ self.runPySimShell(cardname, "test_apdu_legacy_scp03.script", no_exceptions = True, add_csv = True)
+ self.equipTemplate("test_apdu_legacy_scp03.script", SEC_LEVEL = 1)
+ self.runPySimShell(cardname, "test_apdu_legacy_scp03.script", no_exceptions = True, add_csv = True)
+
+ def test_apdu(self):
+ cardname = 'sysmoISIM-SJA5-S17'
+
+ self.runPySimShell(cardname, "test_apdu.script", no_exceptions = True)
+
+ def test_apdu_legacy_scp02(self):
+ cardname = 'sysmoISIM-SJA5-S17'
+
+ self.equipTemplate("test_apdu_scp02.script", SEC_LEVEL = 3)
+ self.runPySimShell(cardname, "test_apdu_scp02.script", no_exceptions = True, add_csv = True)
+ self.equipTemplate("test_apdu_scp02.script", SEC_LEVEL = 1)
+ self.runPySimShell(cardname, "test_apdu_scp02.script", no_exceptions = True, add_csv = True)
+
+ def test_apdu_legacy_scp03(self):
+ cardname = 'sysmoEUICC1-C2T'
+
+ self.equipTemplate("test_apdu_scp03.script", SEC_LEVEL = 3)
+ self.runPySimShell(cardname, "test_apdu_scp03.script", no_exceptions = True, add_csv = True)
+ self.equipTemplate("test_apdu_scp03.script", SEC_LEVEL = 1)
+ self.runPySimShell(cardname, "test_apdu_scp03.script", no_exceptions = True, add_csv = True)
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/tests/pySim-shell_test/apdu/test_apdu.script b/tests/pySim-shell_test/apdu/test_apdu.script
new file mode 100644
index 0000000..a949b2d
--- /dev/null
+++ b/tests/pySim-shell_test/apdu/test_apdu.script
@@ -0,0 +1,20 @@
+set debug true
+set echo true
+set apdu_trace true
+set apdu_strict true
+
+# Case #1: (open channel #1)
+# No command data field, No response data field present
+apdu 00700001 --expect-sw 9000 --expect-response-regex '^$'
+
+# Case #2: (status)
+# No command data field, Response data field present
+apdu 80F2000000 --expect-sw 9000 --expect-response-regex '^[a-fA-F0-9]+$'
+
+# Case #3: (terminal capability)
+# Command data field present, No response data field
+apdu 80AA000005a903830180 --expect-sw 9000 --expect-response-regex '^$'
+
+# Case #4: (select MF)
+# Command data field present, Response data field present
+apdu 00a40004023f0000 --expect-sw 9000 --expect-response-regex '^[a-fA-F0-9]+$'
\ No newline at end of file
diff --git a/tests/pySim-shell_test/apdu/test_apdu_legacy.script b/tests/pySim-shell_test/apdu/test_apdu_legacy.script
new file mode 100644
index 0000000..c3dfb1b
--- /dev/null
+++ b/tests/pySim-shell_test/apdu/test_apdu_legacy.script
@@ -0,0 +1,21 @@
+set debug true
+set echo true
+set apdu_trace true
+
+# Case #1: (open channel #1)
+# No command data field, No response data field present
+# (in ISO/IEC 7816-3 format, this APDU would lack the 0x00 at the end)
+apdu 0070000100 --expect-sw 9000 --expect-response-regex '^$'
+
+# Case #2: (status)
+# No command data field, Response data field present
+apdu 80F2000000 --expect-sw 9000 --expect-response-regex '^[a-fA-F0-9]+$'
+
+# Case #3: (terminal capability)
+# Command data field present, No response data field
+apdu 80AA000005a903830180 --expect-sw 9000 --expect-response-regex '^$'
+
+# Case #4: (select MF)
+# Command data field present, Response data field present
+# (in ISO/IEC 7816-3 format, this APDU would have an additional 0x00 at the end)
+apdu 00a40004023f00 --expect-sw 9000 --expect-response-regex '^[a-fA-F0-9]+$'
\ No newline at end of file
diff --git a/tests/pySim-shell_test/apdu/test_apdu_legacy_scp02.template b/tests/pySim-shell_test/apdu/test_apdu_legacy_scp02.template
new file mode 100644
index 0000000..51497ed
--- /dev/null
+++ b/tests/pySim-shell_test/apdu/test_apdu_legacy_scp02.template
@@ -0,0 +1,27 @@
+set debug true
+set echo true
+set apdu_trace true
+
+# Establish secure channel:
+select ADF.ISD
+establish_scp02 --key-provider-suffix 1 --key-ver 112 --security-level $SEC_LEVEL
+
+# Case #1: (get status with no data field to mimic a case #1 APDU)
+# No command data field, No response data field present
+# (in ISO/IEC 7816-3 format, this APDU would lack the 0x00 at the end)
+apdu 80F2200200 --expect-sw 6a80 --expect-response-regex '^$$'
+
+# Case #2: (get data)
+# No command data field, Response data field present
+apdu 80ca006600 --expect-sw 9000 --expect-response-regex '^[a-fA-F0-9]+$$'
+
+# Case #3: (get status with wrong parameters to mimic a case #3 APDU)
+# Command data field present, No response data field
+apdu 80F220020a4f0212345c054f9f70c5 --expect-sw 6a80 --expect-response-regex '^$$'
+
+# Case #4: (initialize update, to mimic a case #4 APDU, this will unfortunately kill the session but we are done anyway)
+# Command data field present, Response data field present
+# (in ISO/IEC 7816-3 format, this APDU would have an additional 0x00 at the end)
+apdu 805000000855baa7eca1cd629e --expect-sw 9000 --expect-response-regex '^[a-fA-F0-9]+$$'
+
+release_scp
\ No newline at end of file
diff --git a/tests/pySim-shell_test/apdu/test_apdu_legacy_scp03.template b/tests/pySim-shell_test/apdu/test_apdu_legacy_scp03.template
new file mode 100644
index 0000000..b41620b
--- /dev/null
+++ b/tests/pySim-shell_test/apdu/test_apdu_legacy_scp03.template
@@ -0,0 +1,27 @@
+set debug true
+set echo true
+set apdu_trace true
+
+# Establish secure channel:
+select ADF.ISD-R
+establish_scp03 --key-provider-suffix 1 --key-ver 50 --security-level $SEC_LEVEL
+
+# Case #1: (get status with no data field to mimic a case #1 APDU)
+# No command data field, No response data field present
+# (in ISO/IEC 7816-3 format, this APDU would lack the 0x00 at the end)
+apdu 80F2200200 --expect-sw 6a80 --expect-response-regex '^$$'
+
+# Case #2: (get data)
+# No command data field, Response data field present
+apdu 80ca006600 --expect-sw 9000 --expect-response-regex '^[a-fA-F0-9]+$$'
+
+# Case #3: (get status with wrong parameters to mimic a case #3 APDU)
+# Command data field present, No response data field
+apdu 80F220020a4f0212345c054f9f70c5 --expect-sw 6a88 --expect-response-regex '^$$'
+
+# Case #4: (get eid)
+# Command data field present, Response data field present
+# (in ISO/IEC 7816-3 format, this APDU would have an additional 0x00 at the end)
+apdu 80E2910006bf3e035c015a --expect-sw 9000 --expect-response-regex '^[a-fA-F0-9]+$$'
+
+release_scp
\ No newline at end of file
diff --git a/tests/pySim-shell_test/apdu/test_apdu_scp02.template b/tests/pySim-shell_test/apdu/test_apdu_scp02.template
new file mode 100644
index 0000000..fbe6c07
--- /dev/null
+++ b/tests/pySim-shell_test/apdu/test_apdu_scp02.template
@@ -0,0 +1,26 @@
+set debug true
+set echo true
+set apdu_trace true
+set apdu_strict true
+
+# Establish secure channel:
+select ADF.ISD
+establish_scp02 --key-provider-suffix 1 --key-ver 112 --security-level $SEC_LEVEL
+
+# Case #1: (get status with no data field to mimic a case #1 APDU)
+# No command data field, No response data field present
+apdu 80F22002 --expect-sw 6a80 --expect-response-regex '^$$'
+
+# Case #2: (get data)
+# No command data field, Response data field present
+apdu 80ca006600 --expect-sw 9000 --expect-response-regex '^[a-fA-F0-9]+$$'
+
+# Case #3: (get status with wrong parameters to mimic a case #3 APDU)
+# Command data field present, No response data field
+apdu 80F220020a4f0212345c054f9f70c5 --expect-sw 6a80 --expect-response-regex '^$$'
+
+# Case #4: (initialize update, to mimic a case #4 APDU, this will unfortunately kill the session but we are done anyway)
+# Command data field present, Response data field present
+apdu 805000000855baa7eca1cd629e00 --expect-sw 9000 --expect-response-regex '^[a-fA-F0-9]+$$'
+
+release_scp
\ No newline at end of file
diff --git a/tests/pySim-shell_test/apdu/test_apdu_scp03.template b/tests/pySim-shell_test/apdu/test_apdu_scp03.template
new file mode 100644
index 0000000..09b2639
--- /dev/null
+++ b/tests/pySim-shell_test/apdu/test_apdu_scp03.template
@@ -0,0 +1,26 @@
+set debug true
+set echo true
+set apdu_trace true
+set apdu_strict true
+
+# Establish secure channel:
+select ADF.ISD-R
+establish_scp03 --key-provider-suffix 1 --key-ver 50 --security-level $SEC_LEVEL
+
+# Case #1: (get status with no data field to mimic a case #1 APDU)
+# No command data field, No response data field present
+apdu 80F22002 --expect-sw 6a80 --expect-response-regex '^$$'
+
+# Case #2: (get data)
+# No command data field, Response data field present
+apdu 80ca006600 --expect-sw 9000 --expect-response-regex '^[a-fA-F0-9]+$$'
+
+# Case #3: (get status with wrong parameters to mimic a case #3 APDU)
+# Command data field present, No response data field
+apdu 80F220020a4f0212345c054f9f70c5 --expect-sw 6a88 --expect-response-regex '^$$'
+
+# Case #4: (get eid)
+# Command data field present, Response data field present
+apdu 80E2910006bf3e035c015a00 --expect-sw 9000 --expect-response-regex '^[a-fA-F0-9]+$$'
+
+release_scp
\ No newline at end of file
diff --git a/tests/pySim-shell_test/config.yaml b/tests/pySim-shell_test/config.yaml
index 705d1b4..dac22fd 100644
--- a/tests/pySim-shell_test/config.yaml
+++ b/tests/pySim-shell_test/config.yaml
@@ -1,6 +1,6 @@
regenerate: False
keepfiles: False
-print_content: False
+print_content: True
cards:
- name : "sysmoISIM-SJA5-S17"
atr : "3B9F96801F878031E073FE211B674A357530350265F8"
diff --git a/tests/pySim-shell_test/lchan/test.ok b/tests/pySim-shell_test/lchan/test.ok
index 29882cd..2201cb5 100644
--- a/tests/pySim-shell_test/lchan/test.ok
+++ b/tests/pySim-shell_test/lchan/test.ok
@@ -1,8 +1,8 @@
--> 0070000100
+-> 00700001
<- 9000:
--> 0070000200
+-> 00700002
<- 9000:
--> 0070000300
+-> 00700003
<- 9000:
currently selected file: MF/DF.TELECOM/EF.MSISDN (3f00/7f10/6f40)
currently selected file: MF/ADF.USIM/EF.IMSI (3f00/a0000000871002/6f07)
@@ -12,9 +12,9 @@
"response_all_ref_ar_do": null
}
]
--> 0070800100
+-> 00708001
<- 9000:
--> 0070800200
+-> 00708002
<- 9000:
--> 0070800300
+-> 00708003
<- 9000:
diff --git a/tests/unittests/test_globalplatform.py b/tests/unittests/test_globalplatform.py
index c8fdda5..597d51d 100644
--- a/tests/unittests/test_globalplatform.py
+++ b/tests/unittests/test_globalplatform.py
@@ -36,14 +36,14 @@
def test_mutual_auth_success(self):
init_upd_cmd = self.scp02.gen_init_update_apdu(host_challenge=self.host_challenge)
- self.assertEqual(b2h(init_upd_cmd).upper(), '805020000840A62C37FA6304F8')
+ self.assertEqual(b2h(init_upd_cmd).upper(), '805020000840A62C37FA6304F800')
self.scp02.parse_init_update_resp(self.init_update_resp)
ext_auth_cmd = self.scp02.gen_ext_auth_apdu()
self.assertEqual(b2h(ext_auth_cmd).upper(), '8482010010BA6961667737C5BCEBECE14C7D6A4376')
def test_mutual_auth_fail_card_cryptogram(self):
init_upd_cmd = self.scp02.gen_init_update_apdu(host_challenge=self.host_challenge)
- self.assertEqual(b2h(init_upd_cmd).upper(), '805020000840A62C37FA6304F8')
+ self.assertEqual(b2h(init_upd_cmd).upper(), '805020000840A62C37FA6304F800')
wrong_init_update_resp = self.init_update_resp.copy()
wrong_init_update_resp[-1:] = b'\xff'
with self.assertRaises(ValueError):
@@ -61,15 +61,32 @@
ext_auth_cmd = self.scp02.gen_ext_auth_apdu()
def test_mac_command(self):
- wrapped = self.scp02.wrap_cmd_apdu(h2b('80f28002024f00'))
- self.assertEqual(b2h(wrapped).upper(), '84F280020A4F00B21AAFA3EB2D1672')
+ # Case #1: No command data field, No response data field present
+ wrapped = self.scp02.wrap_cmd_apdu(h2b('80F22002'))
+ self.assertEqual(b2h(wrapped).upper(), '84F220020814DB34FA4341DCA8')
+
+ # Case #2: No command data field, Response data field present
+ wrapped = self.scp02.wrap_cmd_apdu(h2b('80ca006600'))
+ self.assertEqual(b2h(wrapped).upper(), '84CA00660855ED7C5FF069512B00')
+
+ # Case #3: Command data field present, No response data field
+ wrapped = self.scp02.wrap_cmd_apdu(h2b('80F220020a4f0212345c054f9f70c5'))
+ self.assertEqual(b2h(wrapped).upper(), '84F22002124F0212345C054F9F70C58FC1B380C4228AF8')
+
+ # Case #4: Command data field present, Response data field present
+ wrapped = self.scp02.wrap_cmd_apdu(h2b('80f28002024f0000'))
+ self.assertEqual(b2h(wrapped).upper(), '84F280020A4F003B95F09317DE6A4E00')
class SCP03_Test:
"""some kind of 'abstract base class' for a unittest.UnitTest, implementing common functionality for all
of our SCP03 test caseses."""
- get_eid_cmd_plain = h2b('80E2910006BF3E035C015A')
+ get_eid_cmd_plain = h2b('80E2910006BF3E035C015A00')
get_eid_rsp_plain = h2b('bf3e125a1089882119900000000000000000000005')
+ case_1_apdu_plain = h2b('80F22002')
+ case_2_apdu_plain = h2b('80ca006600')
+ case_3_apdu_plain = h2b('80F220020a4f0212345c054f9f70c5')
+ case_4_apdu_plain = h2b('80f28002024f0000')
# must be overridden by derived classes
init_upd_cmd = b''
@@ -81,7 +98,7 @@
@property
def host_challenge(self) -> bytes:
- return self.init_upd_cmd[5:]
+ return self.init_upd_cmd[5:-1]
@property
def kvn(self) -> int:
@@ -128,6 +145,21 @@
# pylint: disable=no-member
self.assertEqual(self.get_eid_rsp_plain, self.scp.unwrap_rsp_apdu(h2b('9000'), self.get_eid_rsp))
+ def test_06_mac_command(self):
+ # pylint: disable=no-member
+
+ # Case #1: No command data field, No response data field present
+ self.assertEqual(self.case_1_apdu, self.scp.wrap_cmd_apdu(self.case_1_apdu_plain))
+
+ # Case #2: No command data field, Response data field present
+ self.assertEqual(self.case_2_apdu, self.scp.wrap_cmd_apdu(self.case_2_apdu_plain))
+
+ # Case #3: Command data field present, No response data field
+ self.assertEqual(self.case_3_apdu, self.scp.wrap_cmd_apdu(self.case_3_apdu_plain))
+
+ # Case #4: Command data field present, Response data field present
+ self.assertEqual(self.case_4_apdu, self.scp.wrap_cmd_apdu(self.case_4_apdu_plain))
+
# The SCP03 keysets used for various key lenghs
KEYSET_AES128 = GpCardKeyset(0x30, h2b('000102030405060708090a0b0c0d0e0f'), h2b('101112131415161718191a1b1c1d1e1f'), h2b('202122232425262728292a2b2c2d2e2f'))
@@ -139,75 +171,111 @@
class SCP03_Test_AES128_11(SCP03_Test, unittest.TestCase):
keyset = KEYSET_AES128
- init_upd_cmd = h2b('8050300008b13e5f938fc108c4')
+ init_upd_cmd = h2b('8050300008b13e5f938fc108c400')
init_upd_rsp = h2b('000000000000000000003003703eb51047495b249f66c484c1d2ef1948000002')
ext_auth_cmd = h2b('84821100107d5f5826a993ebc89eea24957fa0b3ce')
- get_eid_cmd = h2b('84e291000ebf3e035c015a558d036518a28297')
+ get_eid_cmd = h2b('84e291000ebf3e035c015a558d036518a2829700')
get_eid_rsp = h2b('bf3e125a1089882119900000000000000000000005971be68992dbbdfa')
+ case_1_apdu = h2b('84f220020863a63f8959827fb2')
+ case_2_apdu = h2b('84ca006608a0c6a4a74166f7ce00')
+ case_3_apdu = h2b('84f22002124f0212345c054f9f70c52249b50272656536')
+ case_4_apdu = h2b('84f280020a4f00e91443f6dce6b8ed00')
class SCP03_Test_AES128_03(SCP03_Test, unittest.TestCase):
keyset = KEYSET_AES128
- init_upd_cmd = h2b('80503000088e1552d0513c60f3')
+ init_upd_cmd = h2b('80503000088e1552d0513c60f300')
init_upd_rsp = h2b('0000000000000000000030037030760cd2c47c1dd395065fe5ead8a9d7000001')
ext_auth_cmd = h2b('8482030010fd4721a14d9b07003c451d2f8ae6bb21')
- get_eid_cmd = h2b('84e2910018ca9c00f6713d79bc8baa642bdff51c3f6a4082d3bd9ad26c')
+ get_eid_cmd = h2b('84e2910018ca9c00f6713d79bc8baa642bdff51c3f6a4082d3bd9ad26c00')
get_eid_rsp = h2b('bf3e125a1089882119900000000000000000000005')
+ case_1_apdu = h2b('84f2200208c9811b11f1264cf1')
+ case_2_apdu = h2b('84ca006608e10ab60b3054798800')
+ case_3_apdu = h2b('84f22002184e2908bdb48b2315a55482e9e936ca122d6ecfae7d17416e')
+ case_4_apdu = h2b('84f28002180dd10a6b6193e5340b9e77d32d5a179cd710ac2773aefb2800')
class SCP03_Test_AES128_33(SCP03_Test, unittest.TestCase):
keyset = KEYSET_AES128
- init_upd_cmd = h2b('8050300008fdf38259a1e0de44')
+ init_upd_cmd = h2b('8050300008fdf38259a1e0de4400')
init_upd_rsp = h2b('000000000000000000003003703b1aca81e821f219081cdc01c26b372d000003')
ext_auth_cmd = h2b('84823300108c36f96bcc00724a4e13ad591d7da3f0')
- get_eid_cmd = h2b('84e2910018267a85dfe4a98fca6fb0527e0dfecce4914e40401433c87f')
+ get_eid_cmd = h2b('84e2910018267a85dfe4a98fca6fb0527e0dfecce4914e40401433c87f00')
get_eid_rsp = h2b('f3ba2b1013aa6224f5e1c138d71805c569e5439b47576260b75fc021b25097cb2e68f8a0144975b9')
+ case_1_apdu = h2b('84f2200208ac6a59024bed84cc')
+ case_2_apdu = h2b('84ca006608409912ad8fb7aed000')
+ case_3_apdu = h2b('84f22002185f3dafc3ac14c381536a488bf44e06d056df9d74dbd21e5a')
+ case_4_apdu = h2b('84f280021865165105be3373347d0424d4400af2ac393f569ec779389e00')
class SCP03_Test_AES192_11(SCP03_Test, unittest.TestCase):
keyset = KEYSET_AES192
- init_upd_cmd = h2b('80503100087396430b768b085b')
+ init_upd_cmd = h2b('80503100087396430b768b085b00')
init_upd_rsp = h2b('000000000000000000003103708cfc23522ffdbf1e5df5542cac8fd866000003')
ext_auth_cmd = h2b('84821100102145ed30b146f5db252fb7e624cec244')
- get_eid_cmd = h2b('84e291000ebf3e035c015aff42cf801d143944')
+ get_eid_cmd = h2b('84e291000ebf3e035c015aff42cf801d14394400')
get_eid_rsp = h2b('bf3e125a1089882119900000000000000000000005162fbd33e04940a9')
+ case_1_apdu = h2b('84f22002084584e4f6784811ee')
+ case_2_apdu = h2b('84ca006608937776ebe190fa3000')
+ case_3_apdu = h2b('84f22002124f0212345c054f9f70c59a52bddf3040368c')
+ case_4_apdu = h2b('84f280020a4f009804b11411f7393d00')
class SCP03_Test_AES192_03(SCP03_Test, unittest.TestCase):
keyset = KEYSET_AES192
- init_upd_cmd = h2b('805031000869c65da8202bf19f')
+ init_upd_cmd = h2b('805031000869c65da8202bf19f00')
init_upd_rsp = h2b('00000000000000000000310370b570a67be38446717729d6dd3d2ec5b1000001')
ext_auth_cmd = h2b('848203001065df4f1a356a887905466516d9e5b7c1')
- get_eid_cmd = h2b('84e2910018d2c6fb477c5d4afe4fd4d21f17eff10d3578ec1774a12a2d')
+ get_eid_cmd = h2b('84e2910018d2c6fb477c5d4afe4fd4d21f17eff10d3578ec1774a12a2d00')
get_eid_rsp = h2b('bf3e125a1089882119900000000000000000000005')
+ case_1_apdu = h2b('84f2200208964e188f0b1bb697')
+ case_2_apdu = h2b('84ca006608f0820035a41d3e1800')
+ case_3_apdu = h2b('84f220021806b076ed452cd1fa84f77f5c08a146aa77a9286757dea791')
+ case_4_apdu = h2b('84f2800218d06527e39222dce091fabdb8e9b898417a67a6852d3577db00')
class SCP03_Test_AES192_33(SCP03_Test, unittest.TestCase):
keyset = KEYSET_AES192
- init_upd_cmd = h2b('80503100089b3f2eef0e8c9374')
+ init_upd_cmd = h2b('80503100089b3f2eef0e8c937400')
init_upd_rsp = h2b('00000000000000000000310370f6bb305a15bae1a68f79fb08212fbed7000002')
ext_auth_cmd = h2b('84823300109100bc22d58b45b86a26365ce39ff3cf')
- get_eid_cmd = h2b('84e29100188f7f946c84f70d17994bc6e8791251bb1bb1bf02cf8de589')
+ get_eid_cmd = h2b('84e29100188f7f946c84f70d17994bc6e8791251bb1bb1bf02cf8de58900')
get_eid_rsp = h2b('c05176c1b6f72aae50c32cbee63b0e95998928fd4dfb2be9f27ffde8c8476f5909b4805cc4039599')
+ case_1_apdu = h2b('84f2200208d5d97754b6b3d2ba')
+ case_2_apdu = h2b('84ca006608516c82b8e30adbeb00')
+ case_3_apdu = h2b('84f2200218cc247f4761e6944277a4e0d6e32e44025b1e31537e2fc668')
+ case_4_apdu = h2b('84f2800218ba22b63d509bef5d093b43e5eaed03ed23144ab2d9cb51de00')
class SCP03_Test_AES256_11(SCP03_Test, unittest.TestCase):
keyset = KEYSET_AES256
- init_upd_cmd = h2b('805032000811666d57866c6f54')
+ init_upd_cmd = h2b('805032000811666d57866c6f5400')
init_upd_rsp = h2b('0000000000000000000032037053ea8847efa7674e41498a4d66cf0dee000003')
ext_auth_cmd = h2b('84821100102f2ad190eff2fafc4908996d1cebd310')
- get_eid_cmd = h2b('84e291000ebf3e035c015af4b680372542b59d')
+ get_eid_cmd = h2b('84e291000ebf3e035c015af4b680372542b59d00')
get_eid_rsp = h2b('bf3e125a10898821199000000000000000000000058012dd7f01f1c4c1')
+ case_1_apdu = h2b('84f2200208d618b7da68d5fe52')
+ case_2_apdu = h2b('84ca0066088f3e055db23ad5e500')
+ case_3_apdu = h2b('84f22002124f0212345c054f9f70c5b6e15cc42404915e')
+ case_4_apdu = h2b('84f280020a4f00aa124aa74afe7f7500')
class SCP03_Test_AES256_03(SCP03_Test, unittest.TestCase):
keyset = KEYSET_AES256
- init_upd_cmd = h2b('8050320008c6066990fc426e1d')
+ init_upd_cmd = h2b('8050320008c6066990fc426e1d00')
init_upd_rsp = h2b('000000000000000000003203708682cd81bbd8919f2de3f2664581f118000001')
ext_auth_cmd = h2b('848203001077c493b632edadaf865a1e64acc07ce9')
- get_eid_cmd = h2b('84e29100183ddaa60594963befaada3525b492ede23c2ab2c1ce3afe44')
+ get_eid_cmd = h2b('84e29100183ddaa60594963befaada3525b492ede23c2ab2c1ce3afe4400')
get_eid_rsp = h2b('bf3e125a1089882119900000000000000000000005')
+ case_1_apdu = h2b('84f2200208480ddc8e419da38d')
+ case_2_apdu = h2b('84ca0066083e9d6a6c0b2d732000')
+ case_3_apdu = h2b('84f22002183ebfef2da8b04af2a85f491f299b76973df76ff08a4031be')
+ case_4_apdu = h2b('84f2800218783fff80990f5585b1055010ea95094a26e4a8f1ef4b18e100')
class SCP03_Test_AES256_33(SCP03_Test, unittest.TestCase):
keyset = KEYSET_AES256
- init_upd_cmd = h2b('805032000897b2055fe58599fd')
+ init_upd_cmd = h2b('805032000897b2055fe58599fd00')
init_upd_rsp = h2b('00000000000000000000320370a8439a22cedf045fa9f1903b2834f26e000002')
ext_auth_cmd = h2b('8482330010508a0fd959d2e547c6b33154a6be2057')
- get_eid_cmd = h2b('84e29100187a5ef717eaf1e135ae92fe54429d0e465decda65f5fe5aea')
+ get_eid_cmd = h2b('84e29100187a5ef717eaf1e135ae92fe54429d0e465decda65f5fe5aea00')
get_eid_rsp = h2b('ea90dbfa648a67c5eb6abc57f8530b97d0cd5647c5e8732016b55203b078dd2ace7f8bc5d1c1cd99')
+ case_1_apdu = h2b('84f2200208bcc5c17275545d93')
+ case_2_apdu = h2b('84ca00660804806aba9d543bb600')
+ case_3_apdu = h2b('84f2200218717222491556ec81a45f49ce48be33320024801a1c4cb0e0')
+ case_4_apdu = h2b('84f2800218561f105bccd3a1642904b251ccc1228beb80a82370a8637000')
# FIXME:
# - for S8 and S16 mode
diff --git a/tests/unittests/test_utils.py b/tests/unittests/test_utils.py
index 30a01d2..04ceba4 100755
--- a/tests/unittests/test_utils.py
+++ b/tests/unittests/test_utils.py
@@ -4,6 +4,7 @@
from pySim import utils
from pySim.legacy import utils as legacy_utils
from pySim.ts_31_102 import EF_SUCI_Calc_Info
+from osmocom.utils import h2b
# we don't really want to thest TS 102 221, but the underlying DataObject codebase
from pySim.ts_102_221 import AM_DO_EF, AM_DO_DF, SC_DO
@@ -189,5 +190,19 @@
# 18 digits; we expect luhn check digit to be added
self.assertEqual(utils.sanitize_iccid('898821100000053008'), '8988211000000530082')
+class TestUtils(unittest.TestCase):
+ def test_parse_command_apdu(self):
+ # Case #1 APDU:
+ self.assertEqual(utils.parse_command_apdu(h2b('41414141')), (1,0,0,h2b('')))
+ # Case #2 APDU:
+ self.assertEqual(utils.parse_command_apdu(h2b('414141410F')), (2,0,15,h2b('')))
+ self.assertEqual(utils.parse_command_apdu(h2b('4141414100')), (2,0,256,h2b('')))
+ # Case #3 APDU:
+ self.assertEqual(utils.parse_command_apdu(h2b('41414141081122334455667788')), (3,8,0,h2b('1122334455667788')))
+ self.assertEqual(utils.parse_command_apdu(h2b('4141414100' + 256 * '42')), (3,256,0,h2b(256 * '42')))
+ # Case #4 APDU:
+ self.assertEqual(utils.parse_command_apdu(h2b('4141414108112233445566778804')), (4,8,4,h2b('1122334455667788')))
+ self.assertEqual(utils.parse_command_apdu(h2b('4141414100' + 256 * '42' + '00')), (4,256,256,h2b(256 * '42')))
+
if __name__ == "__main__":
unittest.main()
--
To view, visit https://gerrit.osmocom.org/c/pysim/+/38657?usp=email
To unsubscribe, or for help writing mail filters, visit https://gerrit.osmocom.org/settings?usp=email
Gerrit-MessageType: newchange
Gerrit-Project: pysim
Gerrit-Branch: master
Gerrit-Change-Id: I8b56d7804a2b4c392f43f8540e0b6e70001a8970
Gerrit-Change-Number: 38657
Gerrit-PatchSet: 1
Gerrit-Owner: dexter <pmaier(a)sysmocom.de>
Attention is currently required from: pespin.
Hello Jenkins Builder,
I'd like you to reexamine a change. Please visit
https://gerrit.osmocom.org/c/osmo-hnbgw/+/38655?usp=email
to look at the new patch set (#2).
The following approvals got outdated and were removed:
Verified-1 by Jenkins Builder
Change subject: Use new libosmo-sigtran APIs to access osmo_ss7_instance
......................................................................
Use new libosmo-sigtran APIs to access osmo_ss7_instance
Depends: libsomo-sigtran.git Change-Id I8f09aff41666822f759f99fa91c13e3b9d89d530
Change-Id: I3549ff3ace641f0b0574a9ddec14120299446434
---
M TODO-RELEASE
M include/osmocom/hnbgw/hnbgw.h
M src/osmo-hnbgw/context_map.c
M src/osmo-hnbgw/hnbgw_cn.c
4 files changed, 23 insertions(+), 16 deletions(-)
git pull ssh://gerrit.osmocom.org:29418/osmo-hnbgw refs/changes/55/38655/2
--
To view, visit https://gerrit.osmocom.org/c/osmo-hnbgw/+/38655?usp=email
To unsubscribe, or for help writing mail filters, visit https://gerrit.osmocom.org/settings?usp=email
Gerrit-MessageType: newpatchset
Gerrit-Project: osmo-hnbgw
Gerrit-Branch: master
Gerrit-Change-Id: I3549ff3ace641f0b0574a9ddec14120299446434
Gerrit-Change-Number: 38655
Gerrit-PatchSet: 2
Gerrit-Owner: pespin <pespin(a)sysmocom.de>
Gerrit-Reviewer: Jenkins Builder
Gerrit-Attention: pespin <pespin(a)sysmocom.de>
Attention is currently required from: pespin.
Hello Jenkins Builder,
I'd like you to reexamine a change. Please visit
https://gerrit.osmocom.org/c/osmo-msc/+/38654?usp=email
to look at the new patch set (#2).
The following approvals got outdated and were removed:
Verified-1 by Jenkins Builder
Change subject: Use new libosmo-sigtran APIs to access osmo_ss7_instance
......................................................................
Use new libosmo-sigtran APIs to access osmo_ss7_instance
Depends: libsomo-sigtran.git Change-Id I617e58f5f3f5420accbfe8b81ee8b3eb553330bf
Change-Id: I4c4f833cfcc4e65ee5372e7736dbef9c65c2f3de
---
M TODO-RELEASE
M src/libmsc/transaction.c
M src/osmo-msc/msc_main.c
3 files changed, 13 insertions(+), 6 deletions(-)
git pull ssh://gerrit.osmocom.org:29418/osmo-msc refs/changes/54/38654/2
--
To view, visit https://gerrit.osmocom.org/c/osmo-msc/+/38654?usp=email
To unsubscribe, or for help writing mail filters, visit https://gerrit.osmocom.org/settings?usp=email
Gerrit-MessageType: newpatchset
Gerrit-Project: osmo-msc
Gerrit-Branch: master
Gerrit-Change-Id: I4c4f833cfcc4e65ee5372e7736dbef9c65c2f3de
Gerrit-Change-Number: 38654
Gerrit-PatchSet: 2
Gerrit-Owner: pespin <pespin(a)sysmocom.de>
Gerrit-Reviewer: Jenkins Builder
Gerrit-Attention: pespin <pespin(a)sysmocom.de>
Attention is currently required from: fixeria.
pespin has posted comments on this change by pespin. ( https://gerrit.osmocom.org/c/python/osmo-python-tests/+/38637?usp=email )
Change subject: Update osmopy module version to match released tag
......................................................................
Patch Set 1:
(1 comment)
Patchset:
PS1:
> Sounds like a good chance to tag a new release?
well if we tagged a new release it would be anyway another new version. In any case, I'm busy with higher prio stuff than releasing this, I think it can wait :)
--
To view, visit https://gerrit.osmocom.org/c/python/osmo-python-tests/+/38637?usp=email
To unsubscribe, or for help writing mail filters, visit https://gerrit.osmocom.org/settings?usp=email
Gerrit-MessageType: comment
Gerrit-Project: python/osmo-python-tests
Gerrit-Branch: master
Gerrit-Change-Id: Idca3f28dad015e626d604e545a0f9b9c9e7bf979
Gerrit-Change-Number: 38637
Gerrit-PatchSet: 1
Gerrit-Owner: pespin <pespin(a)sysmocom.de>
Gerrit-Reviewer: Jenkins Builder
Gerrit-Reviewer: fixeria <vyanitskiy(a)sysmocom.de>
Gerrit-Reviewer: osmith <osmith(a)sysmocom.de>
Gerrit-Attention: fixeria <vyanitskiy(a)sysmocom.de>
Gerrit-Comment-Date: Tue, 05 Nov 2024 16:45:18 +0000
Gerrit-HasComments: Yes
Gerrit-Has-Labels: No
Comment-In-Reply-To: fixeria <vyanitskiy(a)sysmocom.de>