mirror of
https://gitea.osmocom.org/sim-card/pysim.git
synced 2026-09-13 02:53:07 +03:00
GP: fix kcb for non block aligned keys
how encrypt_key() pads a kcv: len(key) % blocksize bytes what it should do to actually do it right: blocksize - len(key) % blocksize so the plaintext handed to the cipher was only block aligned by luck as long as the key length happened to be a multiple of half the block size. And of course decrypt_key() did not invert encrypt_key() at all, the clear text length of GP CardSpec v2.3 Table 11-70 precedes the ENCRYPTED kcv, but it was parsed out of the DECRYPTED data, and the length byte itself was fed to the cipher along with the cryptogram. Fix this up with a helper and tests so it is actually usable. Change-Id: I02b4f2ed948c31e1741e40f0226fb49757fa2570
This commit is contained in:
@@ -215,11 +215,20 @@ class SCP(SecureChannel, abc.ABC):
|
|||||||
def gen_ext_auth_apdu(self, security_level: int = 0x01) -> bytes:
|
def gen_ext_auth_apdu(self, security_level: int = 0x01) -> bytes:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
def pad_to_blocksize(self, data: bytes) -> bytes:
|
||||||
|
"""Right pad the data with zero bytes to a multiple of the DEK cipher block size."""
|
||||||
|
if len(data) % self.sk.blocksize:
|
||||||
|
# not '+=' which would mutate the callers bytearray in place..
|
||||||
|
data = data + b'\x00' * (self.sk.blocksize - len(data) % self.sk.blocksize)
|
||||||
|
return data
|
||||||
|
|
||||||
def encrypt_key(self, key: bytes) -> bytes:
|
def encrypt_key(self, key: bytes) -> bytes:
|
||||||
"""Encrypt a key with the DEK."""
|
"""Encrypt a key with the DEK."""
|
||||||
num_pad = len(key) % self.sk.blocksize
|
if len(key) % self.sk.blocksize:
|
||||||
if num_pad:
|
# The kcv is right padded before encryption and the kcb
|
||||||
return bertlv_encode_len(len(key)) + self.dek_encrypt(key + b'\x00'*num_pad)
|
# is formatted as described in Table 11-70: preceded by the actual length of the
|
||||||
|
# clear text kcv.
|
||||||
|
return bertlv_encode_len(len(key)) + self.dek_encrypt(self.pad_to_blocksize(key))
|
||||||
return self.dek_encrypt(key)
|
return self.dek_encrypt(key)
|
||||||
|
|
||||||
def decrypt_key(self, encrypted_key:bytes) -> bytes:
|
def decrypt_key(self, encrypted_key:bytes) -> bytes:
|
||||||
@@ -232,9 +241,8 @@ class SCP(SecureChannel, abc.ABC):
|
|||||||
# Block provides the actual length of the key component value, which allows recovering the
|
# Block provides the actual length of the key component value, which allows recovering the
|
||||||
# clear-text key component value after decryption of the encrypted key component value and removal
|
# clear-text key component value after decryption of the encrypted key component value and removal
|
||||||
# of padding bytes.
|
# of padding bytes.
|
||||||
decrypted = self.dek_decrypt(encrypted_key)
|
key_len, remainder = bertlv_parse_len(encrypted_key)
|
||||||
key_len, remainder = bertlv_parse_len(decrypted)
|
return self.dek_decrypt(remainder)[:key_len]
|
||||||
return remainder[:key_len]
|
|
||||||
else:
|
else:
|
||||||
# If the length of the Key Component Block is a multiple of the block size of the encryption
|
# If the length of the Key Component Block is a multiple of the block size of the encryption
|
||||||
# algorithm (i.e. 8 bytes for DES, 16 bytes for AES), then it shall be assumed that no padding
|
# algorithm (i.e. 8 bytes for DES, 16 bytes for AES), then it shall be assumed that no padding
|
||||||
|
|||||||
@@ -283,6 +283,41 @@ class SCP03_Test_AES256_33(SCP03_Test, unittest.TestCase):
|
|||||||
# FIXME: test auth with random (0x60) vs pseudo-random (0x70) challenge
|
# FIXME: test auth with random (0x60) vs pseudo-random (0x70) challenge
|
||||||
|
|
||||||
|
|
||||||
|
class KeyComponentBlock_Test(unittest.TestCase):
|
||||||
|
"""Tests for the kcb of GP CardSpec v2.3
|
||||||
|
- Table 11-70 kcv that required padding, preceded by its clear-text length
|
||||||
|
- Table 11-71 no padding required"""
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
# SCP02 (3DES DEK, 8 byte blocks), same vectors as SCP02_Test
|
||||||
|
self.scp02 = SCP02(card_keys=ck_3des_70)
|
||||||
|
self.scp02.gen_init_update_apdu(host_challenge=h2b('40A62C37FA6304F8'))
|
||||||
|
self.scp02.parse_init_update_resp(h2b('00000000000000000000700200016B4524ABEE7CF32EA3838BC148F3'))
|
||||||
|
self.scp02.gen_ext_auth_apdu()
|
||||||
|
# SCP03 (AES DEK, 16 byte blocks), same vectors as SCP03_Test_AES128_11
|
||||||
|
self.scp03 = SCP03(card_keys=KEYSET_AES128)
|
||||||
|
self.scp03.gen_init_update_apdu(h2b('b13e5f938fc108c4'))
|
||||||
|
self.scp03.parse_init_update_resp(h2b('000000000000000000003003703eb51047495b249f66c484c1d2ef1948000002'))
|
||||||
|
self.scp03.gen_ext_auth_apdu(0x11)
|
||||||
|
|
||||||
|
def test_encrypt_decrypt_key(self):
|
||||||
|
for scp in (self.scp02, self.scp03):
|
||||||
|
bs = scp.sk.blocksize
|
||||||
|
for keylen in range(1, 3 * bs + 1):
|
||||||
|
with self.subTest(scp=type(scp).__name__, keylen=keylen):
|
||||||
|
key = bytes(range(keylen))
|
||||||
|
kcb = scp.encrypt_key(key)
|
||||||
|
if keylen % bs:
|
||||||
|
# Table 11-70: <length of clear key component> || <encrypted padded value>
|
||||||
|
self.assertEqual(kcb[0], keylen)
|
||||||
|
self.assertEqual((len(kcb) - 1) % bs, 0)
|
||||||
|
self.assertEqual(len(kcb) - 1, keylen + (bs - keylen % bs))
|
||||||
|
else:
|
||||||
|
# Table 11-71: only the encrypted key component value
|
||||||
|
self.assertEqual(len(kcb), keylen)
|
||||||
|
self.assertEqual(scp.decrypt_key(kcb), key)
|
||||||
|
|
||||||
|
|
||||||
class SCP03_KCV_Test(unittest.TestCase):
|
class SCP03_KCV_Test(unittest.TestCase):
|
||||||
def test_kcv(self):
|
def test_kcv(self):
|
||||||
self.assertEqual(compute_kcv('aes', KEYSET_AES128.enc), h2b('C35280'))
|
self.assertEqual(compute_kcv('aes', KEYSET_AES128.enc), h2b('C35280'))
|
||||||
|
|||||||
Reference in New Issue
Block a user