mirror of
https://gitea.osmocom.org/sim-card/pysim.git
synced 2026-03-17 02:48:34 +03:00
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. As this patch also touches the low level interface to readers it was also manually tested with a classic serial reader. Calypso and AT command readers were not tested. Change-Id: I8b56d7804a2b4c392f43f8540e0b6e70001a8970 Related: OS#6367
This commit is contained in:
@@ -142,8 +142,9 @@ class SimCardCommands:
|
||||
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 @@ class SimCardCommands:
|
||||
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 @@ class SimCardCommands:
|
||||
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 @@ class SimCardCommands:
|
||||
"""
|
||||
|
||||
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 @@ class SimCardCommands:
|
||||
# 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 @@ class SimCardCommands:
|
||||
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 @@ class SimCardCommands:
|
||||
|
||||
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 @@ class SimCardCommands:
|
||||
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:
|
||||
|
||||
@@ -332,7 +332,7 @@ class CardApplicationISDR(pySim.global_platform.CardApplicationSD):
|
||||
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
|
||||
|
||||
@@ -580,7 +580,7 @@ class ADF_SD(CardADF):
|
||||
{'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 @@ class ADF_SD(CardADF):
|
||||
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 @@ class ADF_SD(CardADF):
|
||||
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 @@ class ADF_SD(CardADF):
|
||||
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()
|
||||
|
||||
@@ -24,7 +24,7 @@ from construct import Struct, Bytes, Int8ub, Int16ub, Const
|
||||
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 @@ class SCP02(SCP):
|
||||
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."""
|
||||
@@ -280,9 +280,13 @@ class SCP02(SCP):
|
||||
if not self.do_cmac:
|
||||
return apdu
|
||||
|
||||
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]
|
||||
@@ -298,16 +302,25 @@ class SCP02(SCP):
|
||||
# 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:
|
||||
@@ -454,7 +467,7 @@ class SCP03(SCP):
|
||||
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."""
|
||||
@@ -489,12 +502,16 @@ class SCP03(SCP):
|
||||
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
|
||||
@@ -519,6 +536,11 @@ class SCP03(SCP):
|
||||
apdu = bytes([mcla, ins, p1, p2, mlc]) + cmd_data
|
||||
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:
|
||||
|
||||
@@ -11,7 +11,7 @@ from construct import Construct
|
||||
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 @@ class LinkBase(abc.ABC):
|
||||
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 @@ class LinkBase(abc.ABC):
|
||||
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 @@ class LinkBase(abc.ABC):
|
||||
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 @@ class LinkBase(abc.ABC):
|
||||
raise SwMatchError(rv[1], sw.lower(), self.sw_interpreter)
|
||||
return rv
|
||||
|
||||
|
||||
class LinkBaseTpdu(LinkBase):
|
||||
|
||||
# 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_transparent(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_transparent(self, apdu: Hexstr) -> ResTuple:
|
||||
# In cases where the TPDU format is the same as the APDU format, we may pass the given APDU through without modification
|
||||
# (This is the case for T=1, 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
|
||||
|
||||
@@ -24,7 +24,7 @@ import argparse
|
||||
from typing import Optional
|
||||
from osmocom.utils import h2b, b2h, Hexstr
|
||||
|
||||
from pySim.transport import LinkBase
|
||||
from pySim.transport import LinkBaseTpdu
|
||||
from pySim.exceptions import ReaderError, ProtocolError
|
||||
from pySim.utils import ResTuple
|
||||
|
||||
@@ -70,12 +70,12 @@ class L1CTLMessageSIM(L1CTLMessage):
|
||||
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(LinkBaseTpdu):
|
||||
"""Transport Link for Calypso based phones."""
|
||||
name = 'Calypso-based (OsmocomBB) reader'
|
||||
|
||||
@@ -129,10 +129,10 @@ class CalypsoSimLink(LinkBase):
|
||||
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
|
||||
|
||||
@@ -25,14 +25,14 @@ import serial
|
||||
from osmocom.utils import Hexstr
|
||||
|
||||
from pySim.utils import ResTuple
|
||||
from pySim.transport import LinkBase
|
||||
from pySim.transport import LinkBaseTpdu
|
||||
from pySim.exceptions import ReaderError, ProtocolError
|
||||
|
||||
# HACK: if somebody needs to debug this thing
|
||||
# log.root.setLevel(log.DEBUG)
|
||||
|
||||
|
||||
class ModemATCommandLink(LinkBase):
|
||||
class ModemATCommandLink(LinkBaseTpdu):
|
||||
"""Transport Link for 3GPP TS 27.007 compliant modems."""
|
||||
name = "modem for Generic SIM Access (3GPP TS 27.007)"
|
||||
|
||||
@@ -145,12 +145,12 @@ class ModemATCommandLink(LinkBase):
|
||||
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 @@ class ModemATCommandLink(LinkBase):
|
||||
# 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:
|
||||
|
||||
@@ -30,11 +30,11 @@ from smartcard.ExclusiveConnectCardConnection import ExclusiveConnectCardConnect
|
||||
from osmocom.utils import h2i, i2h, Hexstr
|
||||
|
||||
from pySim.exceptions import NoCardError, ProtocolError, ReaderError
|
||||
from pySim.transport import LinkBase
|
||||
from pySim.transport import LinkBaseTpdu
|
||||
from pySim.utils import ResTuple
|
||||
|
||||
|
||||
class PcscSimLink(LinkBase):
|
||||
class PcscSimLink(LinkBaseTpdu):
|
||||
""" pySim: PCSC reader transport link."""
|
||||
name = 'PC/SC'
|
||||
|
||||
@@ -84,8 +84,19 @@ class PcscSimLink(LinkBase):
|
||||
# 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 @@ class PcscSimLink(LinkBase):
|
||||
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
|
||||
|
||||
@@ -24,11 +24,11 @@ import serial
|
||||
from osmocom.utils import h2b, b2h, Hexstr
|
||||
|
||||
from pySim.exceptions import NoCardError, ProtocolError
|
||||
from pySim.transport import LinkBase
|
||||
from pySim.transport import LinkBaseTpdu
|
||||
from pySim.utils import ResTuple
|
||||
|
||||
|
||||
class SerialSimLink(LinkBase):
|
||||
class SerialSimLink(LinkBaseTpdu):
|
||||
""" pySim: Transport Link for serial (RS232) based readers included with simcard"""
|
||||
name = 'Serial'
|
||||
|
||||
@@ -187,13 +187,13 @@ class SerialSimLink(LinkBase):
|
||||
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 @@ class SerialSimLink(LinkBase):
|
||||
# - 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 @@ class SerialSimLink(LinkBase):
|
||||
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:
|
||||
|
||||
@@ -539,6 +539,52 @@ def boxed_heading_str(heading, width=80):
|
||||
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
|
||||
|
||||
Reference in New Issue
Block a user