mirror of
https://gitea.osmocom.org/sim-card/pysim.git
synced 2026-09-13 06:38:24 +03:00
GP: LOAD/STORE DATA chunk size from SCP overhead
SCP.overhead was so far set at construction time (SCP02: 8, SCP03: s_mode), so the C-MAC length only. Unfortunately sec lvl >= 3 pads the data field to the cipher block size before encryption, so the real worst-case overhead is larger, scc.max_cmd_len (255 - overhead) was too big, and ADF_SD.load() used a hardcoded chunk_len=240. Real world issue with a 286 byte CAP + SCP02 + sec lvl 3: - 240-byte LOAD block is padded to 248, - encrypted - gets 8 byte C-MAC appended -> Lc = 256 That dies with a weird "ValueError: bytes must be in range(0, 256)". The only "fix" for that was to downgrade the seclevel. STORE DATA has the same overflow with large max_cmd_len (247 + padding + MAC = 256 as well). Therefore the overhead must be properly calculated from the sec level. While at it adjust the error in case I missed something to get a more useful ValueError. Change-Id: Ic208f3959a38896f64fb6ccefb24cc360a3ac3a2
This commit is contained in:
@@ -707,6 +707,14 @@ class ADF_SD(CardADF):
|
||||
"""Perform the GlobalPlatform PUT KEY command in order to store a new key on the card.
|
||||
See GlobalPlatform CardSpecification v2.3 Section 11.8 for details."""
|
||||
key_data = self.build_put_key_data(kvn, keys, self._cmd.lchan.scc.scp)
|
||||
# Lc of Table 11-64 is a single byte, while LOAD or STORE DATA splits we can't:
|
||||
# 11.8.2.3.3 splits a key at component boundaries -> not helping here
|
||||
max_cmd_len = self._cmd.lchan.scc.max_cmd_len
|
||||
if len(key_data) > max_cmd_len:
|
||||
raise ValueError('key data field of %u bytes exceeds the maximum command length of %u '
|
||||
'(limited by the overhead of the current secure channel); use fewer '
|
||||
'keys per command, a single key component that large needs STORE DATA' %
|
||||
(len(key_data), max_cmd_len))
|
||||
hdr = "80D8%02x%02x%02x" % (old_kvn, kid, len(key_data))
|
||||
data, _sw = self._cmd.lchan.scc.send_apdu_checksw(hdr + b2h(key_data) + "00")
|
||||
return data
|
||||
@@ -886,23 +894,32 @@ class ADF_SD(CardADF):
|
||||
load_parser_from_grp.add_argument('--from-hex', type=is_hexstr, help='load from hex string')
|
||||
load_parser_from_grp.add_argument('--from-file', type=argparse.FileType('rb', 0), help='load from binary file')
|
||||
load_parser_from_grp.add_argument('--from-cap-file', type=argparse.FileType('rb', 0), help='load from JAVA-card CAP file')
|
||||
load_parser.add_argument('--chunk-len', type=auto_uint8, default=None,
|
||||
help='Block size for the LOAD command; default: as large as the current secure channel overhead permits, at most 240')
|
||||
|
||||
@cmd2.with_argparser(load_parser)
|
||||
def do_load(self, opts):
|
||||
"""Perform a GlobalPlatform LOAD command. (We currently only support loading without DAP and
|
||||
without ciphering.)"""
|
||||
if opts.from_hex is not None:
|
||||
self.load(h2b(opts.from_hex))
|
||||
self.load(h2b(opts.from_hex), opts.chunk_len)
|
||||
elif opts.from_file is not None:
|
||||
self.load(opts.from_file.read())
|
||||
self.load(opts.from_file.read(), opts.chunk_len)
|
||||
elif opts.from_cap_file is not None:
|
||||
cap = CapFile(opts.from_cap_file)
|
||||
self.load(cap.get_loadfile())
|
||||
self.load(cap.get_loadfile(), opts.chunk_len)
|
||||
else:
|
||||
raise ValueError('load source not specified!')
|
||||
|
||||
def load(self, contents:bytes, chunk_len:int = 240):
|
||||
# TODO:tune chunk_len based on the overhead of the used SCP?
|
||||
def load(self, contents:bytes, chunk_len:Optional[int] = None):
|
||||
# scc.max_cmd_len knows the overhead the currently active SCP
|
||||
# 240 is the old default, keep it for now.
|
||||
max_chunk_len = self._cmd.lchan.scc.max_cmd_len
|
||||
if chunk_len is None:
|
||||
chunk_len = min(240, max_chunk_len)
|
||||
elif not 1 <= chunk_len <= max_chunk_len:
|
||||
raise ValueError('chunk_len must be in range 1..%u (limited by the overhead of the current secure channel)' %
|
||||
max_chunk_len)
|
||||
# build TLV according to GPC_SPE_034 section 11.6.2.3 / Table 11-58 for unencrypted case
|
||||
remainder = b'\xC4' + bertlv_encode_len(len(contents)) + contents
|
||||
# transfer this in various chunks to the card
|
||||
@@ -941,6 +958,8 @@ class ADF_SD(CardADF):
|
||||
install_cap_parser_inst_prm_grp.add_argument('--install-parameters-stk',
|
||||
type=is_hexstr, default=None,
|
||||
help='Load Parameters (ETSI TS 102 226, section 8.2.1.3.2.1)')
|
||||
install_cap_parser.add_argument('--chunk-len', type=auto_uint8, default=None,
|
||||
help='Block size for the LOAD command; default: as large as the current secure channel overhead permits, at most 240')
|
||||
|
||||
@cmd2.with_argparser(install_cap_parser)
|
||||
def do_install_cap(self, opts):
|
||||
@@ -979,7 +998,7 @@ class ADF_SD(CardADF):
|
||||
self._cmd.poutput("step #1: install for load...")
|
||||
self.do_install_for_load("--load-file-aid %s --security-domain-aid %s" % (load_file_aid, security_domain_aid))
|
||||
self._cmd.poutput("step #2: load...")
|
||||
self.load(load_file)
|
||||
self.load(load_file, opts.chunk_len)
|
||||
self._cmd.poutput("step #3: install_for_install (and make selectable)...")
|
||||
self.do_install_for_install("--load-file-aid %s --module-aid %s --application-aid %s --install-parameters %s --make-selectable" %
|
||||
(load_file_aid, module_aid, application_aid, install_parameters))
|
||||
|
||||
@@ -182,6 +182,29 @@ class SCP(SecureChannel, abc.ABC):
|
||||
"""Should we perform R-ENC?"""
|
||||
return self.security_level & 0x20
|
||||
|
||||
@property
|
||||
@abc.abstractmethod
|
||||
def mac_len(self) -> int:
|
||||
"""Length of the appended C-MAC, to be provided by derived class."""
|
||||
|
||||
@property
|
||||
def overhead(self) -> int:
|
||||
"""Worst-case len that wrapping a command APDU adds to its data field at the
|
||||
current sec level is (255 - overhead), C-MAC + C-DECRYPTION encryption padding."""
|
||||
if not self.do_cmac:
|
||||
return 0
|
||||
if not self.do_cenc:
|
||||
return self.mac_len
|
||||
# see Secure Channel Protocol '03' Card Specification v2.3 - Amendment D v1.1.2
|
||||
# which defers to GPCS v2.3 Section B.2 which then defers to
|
||||
# NIST SP 800-38B for encryption and points out that
|
||||
# the padding is, as expected, just the usual padding from NIST SP 800-38A
|
||||
# C-DECRYPTION pads with ('80'+['00'...] at least 1 byte) up to
|
||||
# the cipher block size + C-MAC on top -> largest usable data field
|
||||
# is one byte less than the largest block-size multiple within 255 - mac_len.
|
||||
bs = self.sk.blocksize
|
||||
return 255 - ((255 - self.mac_len) // bs * bs - 1)
|
||||
|
||||
def __str__(self) -> str:
|
||||
return "%s[%02x]" % (self.__class__.__name__, self.security_level)
|
||||
|
||||
@@ -268,10 +291,8 @@ class SCP02(SCP):
|
||||
# Key Version Number 0x70 is a non-spec special-case of sysmoISIM-SJA2/SJA5 and possibly more sysmocom products
|
||||
# Key Version Number 0x01 is a non-spec special-case of sysmoUSIM-SJS1
|
||||
kvn_ranges = [[0x01, 0x01], [0x20, 0x2f], [0x70, 0x70]]
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.overhead = 8
|
||||
super().__init__(*args, **kwargs)
|
||||
# C-MAC (Single DES + final 3DES, B.1.2.2) is always one full DES block
|
||||
mac_len = 8
|
||||
|
||||
def dek_encrypt(self, plaintext:bytes) -> bytes:
|
||||
# See also GPC section B.1.1.2, E.4.7, and E.4.1
|
||||
@@ -346,10 +367,16 @@ class SCP02(SCP):
|
||||
# CMAC on modified APDU
|
||||
mlc = lc + 8
|
||||
clac = cla | CLA_SM
|
||||
if mlc >= 256:
|
||||
raise ValueError('Modified Lc (%u) would exceed maximum when appending 8 bytes of mac' % mlc)
|
||||
mac = self.sk.calc_mac_1des(bytes([clac]) + apdu[1:4] + bytes([mlc]) + data)
|
||||
if self.do_cenc:
|
||||
padded_data = pad80(data, 8)
|
||||
if len(padded_data) + 8 >= 256:
|
||||
raise ValueError('Modified Lc (%u) would exceed maximum when appending padding and mac' %
|
||||
(len(padded_data) + 8))
|
||||
k = DES3.new(self.sk.enc, DES.MODE_CBC, b'\x00'*8)
|
||||
data = k.encrypt(pad80(data, 8))
|
||||
data = k.encrypt(padded_data)
|
||||
lc = len(data)
|
||||
|
||||
lc += 8
|
||||
@@ -485,9 +512,13 @@ 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)
|
||||
|
||||
@property
|
||||
def mac_len(self) -> int:
|
||||
# C-MAC truncated to 8 in S8 or 16 bytes in S16 mode
|
||||
return self.s_mode
|
||||
|
||||
def dek_encrypt(self, plaintext:bytes) -> bytes:
|
||||
cipher = AES.new(self.card_keys.dek, AES.MODE_CBC, b'\x00'*16)
|
||||
return cipher.encrypt(plaintext)
|
||||
|
||||
Reference in New Issue
Block a user