dexter has uploaded this change for review. ( https://gerrit.osmocom.org/c/pysim/+/42441?usp=email )
Change subject: transport: change APDU format paradigm ......................................................................
transport: change APDU format paradigm
Unfortunately we have mixed up the concept of TPDUs and APDUs in earlier versions of pySim-shell. This lead to problems with detecteding the APDU case properly (see also ISO/IEC 7816-3) and also prevented us from adding support for T=1.
This problem has been fixed long time ago and all APDUs sent from the pySim-shell code should be well formed and valid according to ISO/IEC 7816-3.
To ensure that we continue to format APDUs correctly as APDUs (and not TPDUs) we have added a mechanism to the LinkBase class that would either raise an exception or print a warning if someone mistakenly tries to send an APDU that is really a TPDU. Whether a warning is printed or an exception is raised is controlled via the apdu_strict member in the LinkBase class, which is false (print warning only) by default.
The reason why we have implemneted the mechanism this way was because we wanted to ensure that existing APDU scripts (pySim-shell apdu command) keep working, even though when those scripts uses APDUs which are formally invalid.
Sending a TPDU instead of an APDU via a T=0 link will still work in almost all cases. This is also the reason why this problem slipped through unnoticed for long time. However, there may still be subtile problems araising from this practice. The root of the problem is that it is impossible to distinguish between APDU case 3 and 4 when a TPDU instead of an APDU is sent. However in order to handle a case 4 APDU correctly we must be able to distinguish the APDU case correctly to handle the case correctly. ETSI TS 102 221, section 7.3.1.1.4, clause 4 is very clear about the fact that not (only) the status word (e.g. 61xx) but the APDU case is what matters.
To complete the logic in LinkBaseTpdu and to maintain compatibility (older APDU scripts), we must still be able to switch between the 'apdu_strict' mode and the non-strict mode. However, it makes sense to do this on a per-api-call basis instead globally via a class property.
At the same time we will limit the effect of pySim-shell's apdu_strict setable to the apdu command only. By doing so, the bahviour of the apdu command is not altered. Users will still have to enable the 'strict' mode explicitly. At the same time all the internal functionality of pySim-shell will always use the 'strict' mode.
Related: OS#6970 Change-Id: I9a531a825def318b28bf58291d811cf119003fab --- M pySim-shell.py M pySim/commands.py M pySim/transport/__init__.py 3 files changed, 24 insertions(+), 28 deletions(-)
git pull ssh://gerrit.osmocom.org:29418/pysim refs/changes/41/42441/1
diff --git a/pySim-shell.py b/pySim-shell.py index 50deea2..39678fd 100755 --- a/pySim-shell.py +++ b/pySim-shell.py @@ -136,8 +136,7 @@ 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)) + 'Strictly apply APDU format according to ISO/IEC 7816-3, table 12', self)) self.add_settable(Settable2Compat('verbose', bool, 'Enable/disable verbose logging', self, onchange_cb=self._onchange_verbose)) @@ -218,13 +217,6 @@ 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 - def _onchange_verbose(self, param_name, old, new): PySimLogger.set_verbose(new) if new == True: @@ -295,9 +287,9 @@ # can be executed without the presence of a runtime state (self.rs) object. However, this also means that # self.lchan is also not present (see method equip). if opts.raw or self.lchan is None: - data, sw = self.card._scc.send_apdu(opts.APDU, apply_lchan = False) + data, sw = self.card._scc.send_apdu(opts.APDU, apply_lchan = False, apdu_strict = self.apdu_strict) else: - data, sw = self.lchan.scc.send_apdu(opts.APDU, apply_lchan = False) + data, sw = self.lchan.scc.send_apdu(opts.APDU, apply_lchan = False, apdu_strict = self.apdu_strict) if data: self.poutput("SW: %s, RESP: %s" % (sw, data)) else: diff --git a/pySim/commands.py b/pySim/commands.py index 066a2c4..b973b93 100644 --- a/pySim/commands.py +++ b/pySim/commands.py @@ -87,7 +87,7 @@ else: return 255
- def send_apdu(self, pdu: Hexstr, apply_lchan:bool = True) -> ResTuple: + def send_apdu(self, pdu: Hexstr, apply_lchan:bool = True, apdu_strict: bool = True) -> ResTuple: """Sends an APDU and auto fetch response data
Args: @@ -101,11 +101,12 @@ if apply_lchan: pdu = cla_with_lchan(pdu[0:2], self.lchan_nr) + pdu[2:] if self.scp: - return self.scp.send_apdu_wrapper(self._tp.send_apdu, pdu) + return self.scp.send_apdu_wrapper(self._tp.send_apdu, pdu, apdu_strict = apdu_strict) else: - return self._tp.send_apdu(pdu) + return self._tp.send_apdu(pdu, apdu_strict = apdu_strict)
- def send_apdu_checksw(self, pdu: Hexstr, sw: SwMatchstr = "9000", apply_lchan:bool = True) -> ResTuple: + def send_apdu_checksw(self, pdu: Hexstr, sw: SwMatchstr = "9000", apply_lchan:bool = True, + apdu_strict: bool = True) -> ResTuple: """Sends an APDU and check returned SW
Args: @@ -121,12 +122,13 @@ if apply_lchan: pdu = cla_with_lchan(pdu[0:2], self.lchan_nr) + pdu[2:] if self.scp: - return self.scp.send_apdu_wrapper(self._tp.send_apdu_checksw, pdu, sw) + return self.scp.send_apdu_wrapper(self._tp.send_apdu_checksw, pdu, sw, apdu_strict = apdu_strict) else: - return self._tp.send_apdu_checksw(pdu, sw) + return self._tp.send_apdu_checksw(pdu, sw, apdu_strict = apdu_strict)
def send_apdu_constr(self, cla: Hexstr, ins: Hexstr, p1: Hexstr, p2: Hexstr, cmd_constr: Construct, - cmd_data: Hexstr, resp_constr: Construct, apply_lchan:bool = True) -> Tuple[dict, SwHexstr]: + cmd_data: Hexstr, resp_constr: Construct, apply_lchan:bool = True, + apdu_strict: bool = True) -> Tuple[dict, SwHexstr]: """Build and sends an APDU using a 'construct' definition; parses response.
Args: @@ -145,7 +147,7 @@ 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) + (data, sw) = self.send_apdu(pdu, apply_lchan = apply_lchan, apdu_strict = apdu_strict) if data: # filter the resulting dict to avoid '_io' members inside rsp = filter_dict(resp_constr.parse(h2b(data))) @@ -155,7 +157,8 @@
def send_apdu_constr_checksw(self, cla: Hexstr, ins: Hexstr, p1: Hexstr, p2: Hexstr, cmd_constr: Construct, cmd_data: Hexstr, resp_constr: Construct, - sw_exp: SwMatchstr="9000", apply_lchan:bool = True) -> Tuple[dict, SwHexstr]: + sw_exp: SwMatchstr="9000", apply_lchan:bool = True, + apdu_strict: bool = True) -> Tuple[dict, SwHexstr]: """Build and sends an APDU using a 'construct' definition; parses response.
Args: @@ -171,7 +174,7 @@ Tuple of (decoded_data, sw) """ (rsp, sw) = self.send_apdu_constr(cla, ins, p1, p2, cmd_constr, cmd_data, resp_constr, - apply_lchan = apply_lchan) + apply_lchan = apply_lchan, apdu_strict = apdu_strict) if not sw_match(sw, sw_exp): raise SwMatchError(sw, sw_exp.lower(), self._tp.sw_interpreter) return (rsp, sw) diff --git a/pySim/transport/__init__.py b/pySim/transport/__init__.py index f19790c..6807048 100644 --- a/pySim/transport/__init__.py +++ b/pySim/transport/__init__.py @@ -90,7 +90,6 @@ 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: @@ -141,11 +140,12 @@ self.apdu_tracer.trace_reset() return self._reset_card()
- def send_apdu(self, apdu: Hexstr) -> ResTuple: + def send_apdu(self, apdu: Hexstr, apdu_strict: bool = True) -> ResTuple: """Sends an APDU with minimal processing
Args: apdu : string of hexadecimal characters (ex. "A0A40000023F00", must comply to ISO/IEC 7816-3, section 12.1) + apdu_strict : strictly apply APDU format according to ISO/IEC 7816-3, table 12 Returns: tuple(data, sw), where data : string (in hex) of returned data (ex. "074F4EFFFF") @@ -174,26 +174,27 @@ 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: + if apdu_strict: raise ValueError(exeption_str) else: log.warning(exeption_str)
return (data, sw)
- def send_apdu_checksw(self, apdu: Hexstr, sw: SwMatchstr = "9000") -> ResTuple: + def send_apdu_checksw(self, apdu: Hexstr, sw: SwMatchstr = "9000", apdu_strict: bool = True) -> ResTuple: """Sends an APDU and check returned SW
Args: 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. + sw : string of 4 hexadecimal characters (ex. "9000"). The user may mask out certain digits using a '?' + to add some ambiguity if needed. + apdu_strict : strictly apply APDU format according to ISO/IEC 7816-3, table 12 Returns: tuple(data, sw), where data : string (in hex) of returned data (ex. "074F4EFFFF") sw : string (in hex) of status word (ex. "9000") """ - rv = self.send_apdu(apdu) + rv = self.send_apdu(apdu, apdu_strict) last_sw = rv[1]
while sw == '9000' and sw_match(last_sw, '91xx'):