ara_m: use class byte of current lchan

The ara_m commands use APDUs with a fix class byte (0x80). This means
that all ARA-M related features only work in the basic logical channel.
To fix this, let's compute the class byte for the current logical channel
dynamically inside the send_apdu methods of SimCardCommands. This will
fix the problem globally.

Related: OS#6531
Change-Id: Ie3e48678f178a488bfaea6cc2b9a3e18145a8d10
This commit is contained in:
Philipp Maier
2024-08-16 14:33:37 +02:00
parent cc4c021bb1
commit caabee4ccb
3 changed files with 33 additions and 26 deletions

View File

@@ -252,9 +252,9 @@ Online manual available at https://downloads.osmocom.org/docs/pysim/master/html/
# can be executed without the presence of a runtime state (self.rs) object. However, this also means that # 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). # self.lchan is also not present (see method equip).
if opts.raw or self.lchan is None: if opts.raw or self.lchan is None:
data, sw = self.card._scc.send_apdu(opts.APDU) data, sw = self.card._scc.send_apdu(opts.APDU, apply_lchan = False)
else: else:
data, sw = self.lchan.scc.send_apdu(opts.APDU) data, sw = self.lchan.scc.send_apdu(opts.APDU, apply_lchan = False)
if data: if data:
self.poutput("SW: %s, RESP: %s" % (sw, data)) self.poutput("SW: %s, RESP: %s" % (sw, data))
else: else:

View File

@@ -262,7 +262,7 @@ class ADF_ARAM(CardADF):
return pySim.global_platform.decode_select_response(data_hex) return pySim.global_platform.decode_select_response(data_hex)
@staticmethod @staticmethod
def xceive_apdu_tlv(tp, hdr: Hexstr, cmd_do, resp_cls, exp_sw='9000'): def xceive_apdu_tlv(scc, hdr: Hexstr, cmd_do, resp_cls, exp_sw='9000'):
"""Transceive an APDU with the card, transparently encoding the command data from TLV """Transceive an APDU with the card, transparently encoding the command data from TLV
and decoding the response data tlv.""" and decoding the response data tlv."""
if cmd_do: if cmd_do:
@@ -274,7 +274,7 @@ class ADF_ARAM(CardADF):
cmd_do_enc = b'' cmd_do_enc = b''
cmd_do_len = 0 cmd_do_len = 0
c_apdu = hdr + ('%02x' % cmd_do_len) + b2h(cmd_do_enc) c_apdu = hdr + ('%02x' % cmd_do_len) + b2h(cmd_do_enc)
(data, _sw) = tp.send_apdu_checksw(c_apdu, exp_sw) (data, _sw) = scc.send_apdu_checksw(c_apdu, exp_sw)
if data: if data:
if resp_cls: if resp_cls:
resp_do = resp_cls() resp_do = resp_cls()
@@ -285,32 +285,32 @@ class ADF_ARAM(CardADF):
return None return None
@staticmethod @staticmethod
def store_data(tp, do) -> bytes: def store_data(scc, do) -> bytes:
"""Build the Command APDU for STORE DATA.""" """Build the Command APDU for STORE DATA."""
return ADF_ARAM.xceive_apdu_tlv(tp, '80e29000', do, StoreResponseDoCollection) return ADF_ARAM.xceive_apdu_tlv(scc, '80e29000', do, StoreResponseDoCollection)
@staticmethod @staticmethod
def get_all(tp): def get_all(scc):
return ADF_ARAM.xceive_apdu_tlv(tp, '80caff40', None, GetResponseDoCollection) return ADF_ARAM.xceive_apdu_tlv(scc, '80caff40', None, GetResponseDoCollection)
@staticmethod @staticmethod
def get_config(tp, v_major=0, v_minor=0, v_patch=1): def get_config(scc, v_major=0, v_minor=0, v_patch=1):
cmd_do = DeviceConfigDO() cmd_do = DeviceConfigDO()
cmd_do.from_val_dict([{'device_interface_version_do': { cmd_do.from_val_dict([{'device_interface_version_do': {
'major': v_major, 'minor': v_minor, 'patch': v_patch}}]) 'major': v_major, 'minor': v_minor, 'patch': v_patch}}])
return ADF_ARAM.xceive_apdu_tlv(tp, '80cadf21', cmd_do, ResponseAramConfigDO) return ADF_ARAM.xceive_apdu_tlv(scc, '80cadf21', cmd_do, ResponseAramConfigDO)
@with_default_category('Application-Specific Commands') @with_default_category('Application-Specific Commands')
class AddlShellCommands(CommandSet): class AddlShellCommands(CommandSet):
def do_aram_get_all(self, _opts): def do_aram_get_all(self, _opts):
"""GET DATA [All] on the ARA-M Applet""" """GET DATA [All] on the ARA-M Applet"""
res_do = ADF_ARAM.get_all(self._cmd.lchan.scc._tp) res_do = ADF_ARAM.get_all(self._cmd.lchan.scc)
if res_do: if res_do:
self._cmd.poutput_json(res_do.to_dict()) self._cmd.poutput_json(res_do.to_dict())
def do_aram_get_config(self, _opts): def do_aram_get_config(self, _opts):
"""Perform GET DATA [Config] on the ARA-M Applet: Tell it our version and retrieve its version.""" """Perform GET DATA [Config] on the ARA-M Applet: Tell it our version and retrieve its version."""
res_do = ADF_ARAM.get_config(self._cmd.lchan.scc._tp) res_do = ADF_ARAM.get_config(self._cmd.lchan.scc)
if res_do: if res_do:
self._cmd.poutput_json(res_do.to_dict()) self._cmd.poutput_json(res_do.to_dict())
@@ -378,14 +378,14 @@ class ADF_ARAM(CardADF):
d = [{'ref_ar_do': [{'ref_do': ref_do_content}, {'ar_do': ar_do_content}]}] d = [{'ref_ar_do': [{'ref_do': ref_do_content}, {'ar_do': ar_do_content}]}]
csrado = CommandStoreRefArDO() csrado = CommandStoreRefArDO()
csrado.from_val_dict(d) csrado.from_val_dict(d)
res_do = ADF_ARAM.store_data(self._cmd.lchan.scc._tp, csrado) res_do = ADF_ARAM.store_data(self._cmd.lchan.scc, csrado)
if res_do: if res_do:
self._cmd.poutput_json(res_do.to_dict()) self._cmd.poutput_json(res_do.to_dict())
def do_aram_delete_all(self, _opts): def do_aram_delete_all(self, _opts):
"""Perform STORE DATA [Command-Delete[all]] to delete all access rules.""" """Perform STORE DATA [Command-Delete[all]] to delete all access rules."""
deldo = CommandDelete() deldo = CommandDelete()
res_do = ADF_ARAM.store_data(self._cmd.lchan.scc._tp, deldo) res_do = ADF_ARAM.store_data(self._cmd.lchan.scc, deldo)
if res_do: if res_do:
self._cmd.poutput_json(res_do.to_dict()) self._cmd.poutput_json(res_do.to_dict())
@@ -479,7 +479,7 @@ class CardApplicationARAM(CardApplication):
export_str = "" export_str = ""
export_str += "aram_delete_all\n" export_str += "aram_delete_all\n"
res_do = ADF_ARAM.get_all(lchan.scc._tp) res_do = ADF_ARAM.get_all(lchan.scc)
if not res_do: if not res_do:
return export_str.strip() return export_str.strip()

View File

@@ -110,26 +110,30 @@ class SimCardCommands:
else: else:
return cla_with_lchan(cla, self.lchan_nr) return cla_with_lchan(cla, self.lchan_nr)
def send_apdu(self, pdu: Hexstr) -> ResTuple: def send_apdu(self, pdu: Hexstr, apply_lchan:bool = True) -> ResTuple:
"""Sends an APDU and auto fetch response data """Sends an APDU and auto fetch response data
Args: Args:
pdu : string of hexadecimal characters (ex. "A0A40000023F00") pdu : string of hexadecimal characters (ex. "A0A40000023F00")
apply_lchan : apply the currently selected lchan to the CLA byte before sending
Returns: Returns:
tuple(data, sw), where tuple(data, sw), where
data : string (in hex) of returned data (ex. "074F4EFFFF") data : string (in hex) of returned data (ex. "074F4EFFFF")
sw : string (in hex) of status word (ex. "9000") sw : string (in hex) of status word (ex. "9000")
""" """
if apply_lchan:
pdu = self.cla4lchan(pdu[0:2]) + pdu[2:]
if self.scp: 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)
else: else:
return self._tp.send_apdu(pdu) return self._tp.send_apdu(pdu)
def send_apdu_checksw(self, pdu: Hexstr, sw: SwMatchstr = "9000") -> ResTuple: def send_apdu_checksw(self, pdu: Hexstr, sw: SwMatchstr = "9000", apply_lchan:bool = True) -> ResTuple:
"""Sends an APDU and check returned SW """Sends an APDU and check returned SW
Args: Args:
pdu : string of hexadecimal characters (ex. "A0A40000023F00") pdu : string of hexadecimal characters (ex. "A0A40000023F00")
apply_lchan : apply the currently selected lchan to the CLA byte before sending
sw : string of 4 hexadecimal characters (ex. "9000"). The user may mask out certain sw : string of 4 hexadecimal characters (ex. "9000"). The user may mask out certain
digits using a '?' to add some ambiguity if needed. digits using a '?' to add some ambiguity if needed.
Returns: Returns:
@@ -137,13 +141,15 @@ class SimCardCommands:
data : string (in hex) of returned data (ex. "074F4EFFFF") data : string (in hex) of returned data (ex. "074F4EFFFF")
sw : string (in hex) of status word (ex. "9000") sw : string (in hex) of status word (ex. "9000")
""" """
if apply_lchan:
pdu = self.cla4lchan(pdu[0:2]) + pdu[2:]
if self.scp: 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)
else: else:
return self._tp.send_apdu_checksw(pdu, sw) return self._tp.send_apdu_checksw(pdu, sw)
def send_apdu_constr(self, cla: Hexstr, ins: Hexstr, p1: Hexstr, p2: Hexstr, cmd_constr: Construct, def send_apdu_constr(self, cla: Hexstr, ins: Hexstr, p1: Hexstr, p2: Hexstr, cmd_constr: Construct,
cmd_data: Hexstr, resp_constr: Construct) -> Tuple[dict, SwHexstr]: cmd_data: Hexstr, resp_constr: Construct, apply_lchan:bool = True) -> Tuple[dict, SwHexstr]:
"""Build and sends an APDU using a 'construct' definition; parses response. """Build and sends an APDU using a 'construct' definition; parses response.
Args: Args:
@@ -154,13 +160,14 @@ class SimCardCommands:
cmd_cosntr : defining how to generate binary APDU command data cmd_cosntr : defining how to generate binary APDU command data
cmd_data : command data passed to cmd_constr cmd_data : command data passed to cmd_constr
resp_cosntr : defining how to decode binary APDU response data resp_cosntr : defining how to decode binary APDU response data
apply_lchan : apply the currently selected lchan to the CLA byte before sending
Returns: Returns:
Tuple of (decoded_data, sw) Tuple of (decoded_data, sw)
""" """
cmd = cmd_constr.build(cmd_data) if cmd_data else '' cmd = cmd_constr.build(cmd_data) if cmd_data else ''
p3 = i2h([len(cmd)]) p3 = i2h([len(cmd)])
pdu = ''.join([cla, ins, p1, p2, p3, b2h(cmd)]) pdu = ''.join([cla, ins, p1, p2, p3, b2h(cmd)])
(data, sw) = self.send_apdu(pdu) (data, sw) = self.send_apdu(pdu, apply_lchan = apply_lchan)
if data: if data:
# filter the resulting dict to avoid '_io' members inside # filter the resulting dict to avoid '_io' members inside
rsp = filter_dict(resp_constr.parse(h2b(data))) rsp = filter_dict(resp_constr.parse(h2b(data)))
@@ -170,7 +177,7 @@ class SimCardCommands:
def send_apdu_constr_checksw(self, cla: Hexstr, ins: Hexstr, p1: Hexstr, p2: Hexstr, def send_apdu_constr_checksw(self, cla: Hexstr, ins: Hexstr, p1: Hexstr, p2: Hexstr,
cmd_constr: Construct, cmd_data: Hexstr, resp_constr: Construct, cmd_constr: Construct, cmd_data: Hexstr, resp_constr: Construct,
sw_exp: SwMatchstr="9000") -> Tuple[dict, SwHexstr]: sw_exp: SwMatchstr="9000", apply_lchan:bool = True) -> Tuple[dict, SwHexstr]:
"""Build and sends an APDU using a 'construct' definition; parses response. """Build and sends an APDU using a 'construct' definition; parses response.
Args: Args:
@@ -185,8 +192,8 @@ class SimCardCommands:
Returns: Returns:
Tuple of (decoded_data, sw) Tuple of (decoded_data, sw)
""" """
(rsp, sw) = self.send_apdu_constr(cla, ins, (rsp, sw) = self.send_apdu_constr(cla, ins, p1, p2, cmd_constr, cmd_data, resp_constr,
p1, p2, cmd_constr, cmd_data, resp_constr) apply_lchan = apply_lchan)
if not sw_match(sw, sw_exp): if not sw_match(sw, sw_exp):
raise SwMatchError(sw, sw_exp.lower(), self._tp.sw_interpreter) raise SwMatchError(sw, sw_exp.lower(), self._tp.sw_interpreter)
return (rsp, sw) return (rsp, sw)
@@ -750,7 +757,7 @@ class SimCardCommands:
Args: Args:
payload : payload as hex string payload : payload as hex string
""" """
return self.send_apdu_checksw('80c20000%02x%s' % (len(payload)//2, payload)) return self.send_apdu_checksw('80c20000%02x%s' % (len(payload)//2, payload), apply_lchan = False)
def terminal_profile(self, payload: Hexstr) -> ResTuple: def terminal_profile(self, payload: Hexstr) -> ResTuple:
"""Send TERMINAL PROFILE to card """Send TERMINAL PROFILE to card
@@ -759,7 +766,7 @@ class SimCardCommands:
payload : payload as hex string payload : payload as hex string
""" """
data_length = len(payload) // 2 data_length = len(payload) // 2
data, sw = self.send_apdu_checksw(('80100000%02x' % data_length) + payload) data, sw = self.send_apdu_checksw(('80100000%02x' % data_length) + payload, apply_lchan = False)
return (data, sw) return (data, sw)
# ETSI TS 102 221 11.1.22 # ETSI TS 102 221 11.1.22
@@ -797,7 +804,7 @@ class SimCardCommands:
raise ValueError('Time unit must be 0x00..0x04') raise ValueError('Time unit must be 0x00..0x04')
min_dur_enc = encode_duration(min_len_secs) min_dur_enc = encode_duration(min_len_secs)
max_dur_enc = encode_duration(max_len_secs) max_dur_enc = encode_duration(max_len_secs)
data, sw = self.send_apdu_checksw('8076000004' + min_dur_enc + max_dur_enc) data, sw = self.send_apdu_checksw('8076000004' + min_dur_enc + max_dur_enc, apply_lchan = False)
negotiated_duration_secs = decode_duration(data[:4]) negotiated_duration_secs = decode_duration(data[:4])
resume_token = data[4:] resume_token = data[4:]
return (negotiated_duration_secs, resume_token, sw) return (negotiated_duration_secs, resume_token, sw)
@@ -807,7 +814,7 @@ class SimCardCommands:
"""Send SUSPEND UICC (resume) to the card.""" """Send SUSPEND UICC (resume) to the card."""
if len(h2b(token)) != 8: if len(h2b(token)) != 8:
raise ValueError("Token must be 8 bytes long") raise ValueError("Token must be 8 bytes long")
data, sw = self.send_apdu_checksw('8076010008' + token) data, sw = self.send_apdu_checksw('8076010008' + token, apply_lchan = False)
return (data, sw) return (data, sw)
def get_data(self, tag: int, cla: int = 0x00): def get_data(self, tag: int, cla: int = 0x00):