Dynamically determine maximum CMD data length depending on SCP

If we're using a Secure Channel Protocol, this will add overhead
in terms of the C-MAC appended to the C-APDU.  This means in turn that
the useable length of the data field shrinks by a certain number of
bytes.

Let's make sure the SCP instances expose an 'overhead' property
of how much overhead they add - and that other commands use this to
determine the maximum command data field length.

Change-Id: I0a081a23efe20c77557600e62b52ba90a401058d
This commit is contained in:
Harald Welte 2024-02-15 20:32:41 +01:00
parent 1432af5150
commit 979c837286
3 changed files with 20 additions and 6 deletions

View File

@ -84,6 +84,14 @@ class SimCardCommands:
"""Return the (cached) patched default CLA byte for this card."""
return self._cla4lchan
@property
def max_cmd_len(self) -> int:
"""Maximum length of the command apdu data section. Depends on secure channel protocol used."""
if self.scp:
return 255 - self.scp.overhead
else:
return 255
@cla_byte.setter
def cla_byte(self, new_val: Hexstr):
"""Set the (raw, without lchan) default CLA value for this card."""
@ -318,7 +326,7 @@ class SimCardCommands:
total_data = ''
chunk_offset = 0
while chunk_offset < length:
chunk_len = min(255, length-chunk_offset)
chunk_len = min(self.max_cmd_len, length-chunk_offset)
pdu = self.cla_byte + \
'b0%04x%02x' % (offset + chunk_offset, chunk_len)
try:
@ -376,7 +384,7 @@ class SimCardCommands:
total_data = ''
chunk_offset = 0
while chunk_offset < data_length:
chunk_len = min(255, data_length - chunk_offset)
chunk_len = min(self.max_cmd_len, data_length - chunk_offset)
# chunk_offset is bytes, but data slicing is hex chars, so we need to multiply by 2
pdu = self.cla_byte + \
'd6%04x%02x' % (offset + chunk_offset, chunk_len) + \
@ -560,10 +568,10 @@ class SimCardCommands:
total_len = len(tlv_bin)
remaining = tlv_bin
while len(remaining) > 0:
fragment = remaining[:255]
fragment = remaining[:self.max_cmd_len]
rdata, sw = self._set_data(fragment, first=first)
first = False
remaining = remaining[255:]
remaining = remaining[self.max_cmd_len:]
return rdata, sw
def run_gsm(self, rand: Hexstr) -> ResTuple:

View File

@ -492,13 +492,14 @@ class ADF_SD(CardADF):
def store_data(self, data: bytes, structure:str = 'none', encryption:str = 'none', response_permitted: bool = False) -> bytes:
"""Perform the GlobalPlatform GET DATA command in order to store some card-specific data.
See GlobalPlatform CardSpecification v2.3Section 11.11 for details."""
max_cmd_len = self._cmd.lchan.scc.max_cmd_len
# Table 11-89 of GP Card Specification v2.3
remainder = data
block_nr = 0
response = ''
while len(remainder):
chunk = remainder[:255]
remainder = remainder[255:]
chunk = remainder[:max_cmd_len]
remainder = remainder[max_cmd_len:]
p1b = build_construct(ADF_SD.StoreData,
{'last_block': len(remainder) == 0, 'encryption': encryption,
'structure': structure, 'response': response_permitted})

View File

@ -218,6 +218,10 @@ class SCP02(SCP):
'seq_counter'/Int16ub, 'card_challenge'/Bytes(6), 'card_cryptogram'/Bytes(8))
kvn_range = [0x20, 0x2f]
def __init__(self, *args, **kwargs):
self.overhead = 8
super().__init__(*args, **kwargs)
def dek_encrypt(self, plaintext:bytes) -> bytes:
cipher = DES.new(self.card_keys.dek, DES.MODE_ECB)
return cipher.encrypt(plaintext)
@ -410,6 +414,7 @@ class SCP03(SCP):
def __init__(self, *args, **kwargs):
self.s_mode = kwargs.pop('s_mode', 8)
self.overhead = self.s_mode
super().__init__(*args, **kwargs)
def dek_encrypt(self, plaintext:bytes) -> bytes: