diff --git a/pySim/global_platform/scp.py b/pySim/global_platform/scp.py index a5fcf51f..fd561139 100644 --- a/pySim/global_platform/scp.py +++ b/pySim/global_platform/scp.py @@ -215,11 +215,20 @@ class SCP(SecureChannel, abc.ABC): def gen_ext_auth_apdu(self, security_level: int = 0x01) -> bytes: 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: """Encrypt a key with the DEK.""" - num_pad = len(key) % self.sk.blocksize - if num_pad: - return bertlv_encode_len(len(key)) + self.dek_encrypt(key + b'\x00'*num_pad) + if len(key) % self.sk.blocksize: + # The kcv is right padded before encryption and the kcb + # 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) 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 # clear-text key component value after decryption of the encrypted key component value and removal # of padding bytes. - decrypted = self.dek_decrypt(encrypted_key) - key_len, remainder = bertlv_parse_len(decrypted) - return remainder[:key_len] + key_len, remainder = bertlv_parse_len(encrypted_key) + return self.dek_decrypt(remainder)[:key_len] else: # 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 diff --git a/tests/unittests/test_globalplatform.py b/tests/unittests/test_globalplatform.py index 8698470d..576407d6 100644 --- a/tests/unittests/test_globalplatform.py +++ b/tests/unittests/test_globalplatform.py @@ -283,6 +283,41 @@ class SCP03_Test_AES256_33(SCP03_Test, unittest.TestCase): # 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: || + 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): def test_kcv(self): self.assertEqual(compute_kcv('aes', KEYSET_AES128.enc), h2b('C35280'))