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
294 lines
14 KiB
Python
294 lines
14 KiB
Python
#!/usr/bin/env python3
|
|
|
|
# (C) 2023-2024 by Harald Welte <laforge@osmocom.org>
|
|
#
|
|
# This program is free software: you can redistribute it and/or modify
|
|
# it under the terms of the GNU General Public License as published by
|
|
# the Free Software Foundation, either version 2 of the License, or
|
|
# (at your option) any later version.
|
|
#
|
|
# This program is distributed in the hope that it will be useful,
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
# GNU General Public License for more details.
|
|
#
|
|
# You should have received a copy of the GNU General Public License
|
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
import unittest
|
|
import logging
|
|
from osmocom.utils import b2h, h2b
|
|
|
|
from pySim.global_platform import *
|
|
from pySim.global_platform.scp import *
|
|
|
|
KIC = h2b('100102030405060708090a0b0c0d0e0f') # enc
|
|
KID = h2b('101102030405060708090a0b0c0d0e0f') # MAC
|
|
KIK = h2b('102102030405060708090a0b0c0d0e0f') # DEK
|
|
ck_3des_70 = GpCardKeyset(0x20, KIC, KID, KIK)
|
|
|
|
class SCP02_Auth_Test(unittest.TestCase):
|
|
host_challenge = h2b('40A62C37FA6304F8')
|
|
init_update_resp = h2b('00000000000000000000700200016B4524ABEE7CF32EA3838BC148F3')
|
|
|
|
def setUp(self):
|
|
self.scp02 = SCP02(card_keys=ck_3des_70)
|
|
|
|
def test_mutual_auth_success(self):
|
|
init_upd_cmd = self.scp02.gen_init_update_apdu(host_challenge=self.host_challenge)
|
|
self.assertEqual(b2h(init_upd_cmd).upper(), '805020000840A62C37FA6304F800')
|
|
self.scp02.parse_init_update_resp(self.init_update_resp)
|
|
ext_auth_cmd = self.scp02.gen_ext_auth_apdu()
|
|
self.assertEqual(b2h(ext_auth_cmd).upper(), '8482010010BA6961667737C5BCEBECE14C7D6A4376')
|
|
|
|
def test_mutual_auth_fail_card_cryptogram(self):
|
|
init_upd_cmd = self.scp02.gen_init_update_apdu(host_challenge=self.host_challenge)
|
|
self.assertEqual(b2h(init_upd_cmd).upper(), '805020000840A62C37FA6304F800')
|
|
wrong_init_update_resp = self.init_update_resp.copy()
|
|
wrong_init_update_resp[-1:] = b'\xff'
|
|
with self.assertRaises(ValueError):
|
|
self.scp02.parse_init_update_resp(wrong_init_update_resp)
|
|
|
|
|
|
class SCP02_Test(unittest.TestCase):
|
|
host_challenge = h2b('40A62C37FA6304F8')
|
|
init_update_resp = h2b('00000000000000000000700200016B4524ABEE7CF32EA3838BC148F3')
|
|
|
|
def setUp(self):
|
|
self.scp02 = SCP02(card_keys=ck_3des_70)
|
|
init_upd_cmd = self.scp02.gen_init_update_apdu(host_challenge=self.host_challenge)
|
|
self.scp02.parse_init_update_resp(self.init_update_resp)
|
|
ext_auth_cmd = self.scp02.gen_ext_auth_apdu()
|
|
|
|
def test_mac_command(self):
|
|
# Case #1: No command data field, No response data field present
|
|
wrapped = self.scp02.wrap_cmd_apdu(h2b('80F22002'))
|
|
self.assertEqual(b2h(wrapped).upper(), '84F220020814DB34FA4341DCA8')
|
|
|
|
# Case #2: No command data field, Response data field present
|
|
wrapped = self.scp02.wrap_cmd_apdu(h2b('80ca006600'))
|
|
self.assertEqual(b2h(wrapped).upper(), '84CA00660855ED7C5FF069512B00')
|
|
|
|
# Case #3: Command data field present, No response data field
|
|
wrapped = self.scp02.wrap_cmd_apdu(h2b('80F220020a4f0212345c054f9f70c5'))
|
|
self.assertEqual(b2h(wrapped).upper(), '84F22002124F0212345C054F9F70C58FC1B380C4228AF8')
|
|
|
|
# Case #4: Command data field present, Response data field present
|
|
wrapped = self.scp02.wrap_cmd_apdu(h2b('80f28002024f0000'))
|
|
self.assertEqual(b2h(wrapped).upper(), '84F280020A4F003B95F09317DE6A4E00')
|
|
|
|
|
|
class SCP03_Test:
|
|
"""some kind of 'abstract base class' for a unittest.UnitTest, implementing common functionality for all
|
|
of our SCP03 test caseses."""
|
|
get_eid_cmd_plain = h2b('80E2910006BF3E035C015A00')
|
|
get_eid_rsp_plain = h2b('bf3e125a1089882119900000000000000000000005')
|
|
case_1_apdu_plain = h2b('80F22002')
|
|
case_2_apdu_plain = h2b('80ca006600')
|
|
case_3_apdu_plain = h2b('80F220020a4f0212345c054f9f70c5')
|
|
case_4_apdu_plain = h2b('80f28002024f0000')
|
|
|
|
# must be overridden by derived classes
|
|
init_upd_cmd = b''
|
|
init_upd_rsp = b''
|
|
ext_auth_cmd = b''
|
|
get_eid_cmd = b''
|
|
get_eid_rsp = b''
|
|
keyset = None
|
|
|
|
@property
|
|
def host_challenge(self) -> bytes:
|
|
return self.init_upd_cmd[5:-1]
|
|
|
|
@property
|
|
def kvn(self) -> int:
|
|
return self.init_upd_cmd[2]
|
|
|
|
@property
|
|
def security_level(self) -> int:
|
|
return self.ext_auth_cmd[2]
|
|
|
|
@property
|
|
def card_challenge(self) -> bytes:
|
|
if len(self.init_upd_rsp) in [10+3+8+8, 10+3+8+8+3]:
|
|
return self.init_upd_rsp[10+3:10+3+8]
|
|
else:
|
|
return self.init_upd_rsp[10+3:10+3+16]
|
|
|
|
@property
|
|
def card_cryptogram(self) -> bytes:
|
|
if len(self.init_upd_rsp) in [10+3+8+8, 10+3+8+8+3]:
|
|
return self.init_upd_rsp[10+3+8:10+3+8+8]
|
|
else:
|
|
return self.init_upd_rsp[10+3+16:10+3+16+16]
|
|
|
|
@classmethod
|
|
def setUpClass(cls):
|
|
cls.scp = SCP03(card_keys = cls.keyset)
|
|
|
|
def test_01_initialize_update(self):
|
|
# pylint: disable=no-member
|
|
self.assertEqual(self.init_upd_cmd, self.scp.gen_init_update_apdu(self.host_challenge))
|
|
|
|
def test_02_parse_init_upd_resp(self):
|
|
self.scp.parse_init_update_resp(self.init_upd_rsp)
|
|
|
|
def test_03_gen_ext_auth_apdu(self):
|
|
# pylint: disable=no-member
|
|
self.assertEqual(self.ext_auth_cmd, self.scp.gen_ext_auth_apdu(self.security_level))
|
|
|
|
def test_04_wrap_cmd_apdu_get_eid(self):
|
|
# pylint: disable=no-member
|
|
self.assertEqual(self.get_eid_cmd, self.scp.wrap_cmd_apdu(self.get_eid_cmd_plain))
|
|
|
|
def test_05_unwrap_rsp_apdu_get_eid(self):
|
|
# pylint: disable=no-member
|
|
self.assertEqual(self.get_eid_rsp_plain, self.scp.unwrap_rsp_apdu(h2b('9000'), self.get_eid_rsp))
|
|
|
|
def test_06_mac_command(self):
|
|
# pylint: disable=no-member
|
|
|
|
# Case #1: No command data field, No response data field present
|
|
self.assertEqual(self.case_1_apdu, self.scp.wrap_cmd_apdu(self.case_1_apdu_plain))
|
|
|
|
# Case #2: No command data field, Response data field present
|
|
self.assertEqual(self.case_2_apdu, self.scp.wrap_cmd_apdu(self.case_2_apdu_plain))
|
|
|
|
# Case #3: Command data field present, No response data field
|
|
self.assertEqual(self.case_3_apdu, self.scp.wrap_cmd_apdu(self.case_3_apdu_plain))
|
|
|
|
# Case #4: Command data field present, Response data field present
|
|
self.assertEqual(self.case_4_apdu, self.scp.wrap_cmd_apdu(self.case_4_apdu_plain))
|
|
|
|
|
|
# The SCP03 keysets used for various key lenghs
|
|
KEYSET_AES128 = GpCardKeyset(0x30, h2b('000102030405060708090a0b0c0d0e0f'), h2b('101112131415161718191a1b1c1d1e1f'), h2b('202122232425262728292a2b2c2d2e2f'))
|
|
KEYSET_AES192 = GpCardKeyset(0x31, h2b('000102030405060708090a0b0c0d0e0f0001020304050607'),
|
|
h2b('101112131415161718191a1b1c1d1e1f1011121314151617'), h2b('202122232425262728292a2b2c2d2e2f2021222324252627'))
|
|
KEYSET_AES256 = GpCardKeyset(0x32, h2b('000102030405060708090a0b0c0d0e0f000102030405060708090a0b0c0d0e0f'),
|
|
h2b('101112131415161718191a1b1c1d1e1f101112131415161718191a1b1c1d1e1f'),
|
|
h2b('202122232425262728292a2b2c2d2e2f202122232425262728292a2b2c2d2e2f'))
|
|
|
|
class SCP03_Test_AES128_11(SCP03_Test, unittest.TestCase):
|
|
keyset = KEYSET_AES128
|
|
init_upd_cmd = h2b('8050300008b13e5f938fc108c400')
|
|
init_upd_rsp = h2b('000000000000000000003003703eb51047495b249f66c484c1d2ef1948000002')
|
|
ext_auth_cmd = h2b('84821100107d5f5826a993ebc89eea24957fa0b3ce')
|
|
get_eid_cmd = h2b('84e291000ebf3e035c015a558d036518a2829700')
|
|
get_eid_rsp = h2b('bf3e125a1089882119900000000000000000000005971be68992dbbdfa')
|
|
case_1_apdu = h2b('84f220020863a63f8959827fb2')
|
|
case_2_apdu = h2b('84ca006608a0c6a4a74166f7ce00')
|
|
case_3_apdu = h2b('84f22002124f0212345c054f9f70c52249b50272656536')
|
|
case_4_apdu = h2b('84f280020a4f00e91443f6dce6b8ed00')
|
|
|
|
class SCP03_Test_AES128_03(SCP03_Test, unittest.TestCase):
|
|
keyset = KEYSET_AES128
|
|
init_upd_cmd = h2b('80503000088e1552d0513c60f300')
|
|
init_upd_rsp = h2b('0000000000000000000030037030760cd2c47c1dd395065fe5ead8a9d7000001')
|
|
ext_auth_cmd = h2b('8482030010fd4721a14d9b07003c451d2f8ae6bb21')
|
|
get_eid_cmd = h2b('84e2910018ca9c00f6713d79bc8baa642bdff51c3f6a4082d3bd9ad26c00')
|
|
get_eid_rsp = h2b('bf3e125a1089882119900000000000000000000005')
|
|
case_1_apdu = h2b('84f2200208c9811b11f1264cf1')
|
|
case_2_apdu = h2b('84ca006608e10ab60b3054798800')
|
|
case_3_apdu = h2b('84f22002184e2908bdb48b2315a55482e9e936ca122d6ecfae7d17416e')
|
|
case_4_apdu = h2b('84f28002180dd10a6b6193e5340b9e77d32d5a179cd710ac2773aefb2800')
|
|
|
|
class SCP03_Test_AES128_33(SCP03_Test, unittest.TestCase):
|
|
keyset = KEYSET_AES128
|
|
init_upd_cmd = h2b('8050300008fdf38259a1e0de4400')
|
|
init_upd_rsp = h2b('000000000000000000003003703b1aca81e821f219081cdc01c26b372d000003')
|
|
ext_auth_cmd = h2b('84823300108c36f96bcc00724a4e13ad591d7da3f0')
|
|
get_eid_cmd = h2b('84e2910018267a85dfe4a98fca6fb0527e0dfecce4914e40401433c87f00')
|
|
get_eid_rsp = h2b('f3ba2b1013aa6224f5e1c138d71805c569e5439b47576260b75fc021b25097cb2e68f8a0144975b9')
|
|
case_1_apdu = h2b('84f2200208ac6a59024bed84cc')
|
|
case_2_apdu = h2b('84ca006608409912ad8fb7aed000')
|
|
case_3_apdu = h2b('84f22002185f3dafc3ac14c381536a488bf44e06d056df9d74dbd21e5a')
|
|
case_4_apdu = h2b('84f280021865165105be3373347d0424d4400af2ac393f569ec779389e00')
|
|
|
|
class SCP03_Test_AES192_11(SCP03_Test, unittest.TestCase):
|
|
keyset = KEYSET_AES192
|
|
init_upd_cmd = h2b('80503100087396430b768b085b00')
|
|
init_upd_rsp = h2b('000000000000000000003103708cfc23522ffdbf1e5df5542cac8fd866000003')
|
|
ext_auth_cmd = h2b('84821100102145ed30b146f5db252fb7e624cec244')
|
|
get_eid_cmd = h2b('84e291000ebf3e035c015aff42cf801d14394400')
|
|
get_eid_rsp = h2b('bf3e125a1089882119900000000000000000000005162fbd33e04940a9')
|
|
case_1_apdu = h2b('84f22002084584e4f6784811ee')
|
|
case_2_apdu = h2b('84ca006608937776ebe190fa3000')
|
|
case_3_apdu = h2b('84f22002124f0212345c054f9f70c59a52bddf3040368c')
|
|
case_4_apdu = h2b('84f280020a4f009804b11411f7393d00')
|
|
|
|
class SCP03_Test_AES192_03(SCP03_Test, unittest.TestCase):
|
|
keyset = KEYSET_AES192
|
|
init_upd_cmd = h2b('805031000869c65da8202bf19f00')
|
|
init_upd_rsp = h2b('00000000000000000000310370b570a67be38446717729d6dd3d2ec5b1000001')
|
|
ext_auth_cmd = h2b('848203001065df4f1a356a887905466516d9e5b7c1')
|
|
get_eid_cmd = h2b('84e2910018d2c6fb477c5d4afe4fd4d21f17eff10d3578ec1774a12a2d00')
|
|
get_eid_rsp = h2b('bf3e125a1089882119900000000000000000000005')
|
|
case_1_apdu = h2b('84f2200208964e188f0b1bb697')
|
|
case_2_apdu = h2b('84ca006608f0820035a41d3e1800')
|
|
case_3_apdu = h2b('84f220021806b076ed452cd1fa84f77f5c08a146aa77a9286757dea791')
|
|
case_4_apdu = h2b('84f2800218d06527e39222dce091fabdb8e9b898417a67a6852d3577db00')
|
|
|
|
class SCP03_Test_AES192_33(SCP03_Test, unittest.TestCase):
|
|
keyset = KEYSET_AES192
|
|
init_upd_cmd = h2b('80503100089b3f2eef0e8c937400')
|
|
init_upd_rsp = h2b('00000000000000000000310370f6bb305a15bae1a68f79fb08212fbed7000002')
|
|
ext_auth_cmd = h2b('84823300109100bc22d58b45b86a26365ce39ff3cf')
|
|
get_eid_cmd = h2b('84e29100188f7f946c84f70d17994bc6e8791251bb1bb1bf02cf8de58900')
|
|
get_eid_rsp = h2b('c05176c1b6f72aae50c32cbee63b0e95998928fd4dfb2be9f27ffde8c8476f5909b4805cc4039599')
|
|
case_1_apdu = h2b('84f2200208d5d97754b6b3d2ba')
|
|
case_2_apdu = h2b('84ca006608516c82b8e30adbeb00')
|
|
case_3_apdu = h2b('84f2200218cc247f4761e6944277a4e0d6e32e44025b1e31537e2fc668')
|
|
case_4_apdu = h2b('84f2800218ba22b63d509bef5d093b43e5eaed03ed23144ab2d9cb51de00')
|
|
|
|
class SCP03_Test_AES256_11(SCP03_Test, unittest.TestCase):
|
|
keyset = KEYSET_AES256
|
|
init_upd_cmd = h2b('805032000811666d57866c6f5400')
|
|
init_upd_rsp = h2b('0000000000000000000032037053ea8847efa7674e41498a4d66cf0dee000003')
|
|
ext_auth_cmd = h2b('84821100102f2ad190eff2fafc4908996d1cebd310')
|
|
get_eid_cmd = h2b('84e291000ebf3e035c015af4b680372542b59d00')
|
|
get_eid_rsp = h2b('bf3e125a10898821199000000000000000000000058012dd7f01f1c4c1')
|
|
case_1_apdu = h2b('84f2200208d618b7da68d5fe52')
|
|
case_2_apdu = h2b('84ca0066088f3e055db23ad5e500')
|
|
case_3_apdu = h2b('84f22002124f0212345c054f9f70c5b6e15cc42404915e')
|
|
case_4_apdu = h2b('84f280020a4f00aa124aa74afe7f7500')
|
|
|
|
class SCP03_Test_AES256_03(SCP03_Test, unittest.TestCase):
|
|
keyset = KEYSET_AES256
|
|
init_upd_cmd = h2b('8050320008c6066990fc426e1d00')
|
|
init_upd_rsp = h2b('000000000000000000003203708682cd81bbd8919f2de3f2664581f118000001')
|
|
ext_auth_cmd = h2b('848203001077c493b632edadaf865a1e64acc07ce9')
|
|
get_eid_cmd = h2b('84e29100183ddaa60594963befaada3525b492ede23c2ab2c1ce3afe4400')
|
|
get_eid_rsp = h2b('bf3e125a1089882119900000000000000000000005')
|
|
case_1_apdu = h2b('84f2200208480ddc8e419da38d')
|
|
case_2_apdu = h2b('84ca0066083e9d6a6c0b2d732000')
|
|
case_3_apdu = h2b('84f22002183ebfef2da8b04af2a85f491f299b76973df76ff08a4031be')
|
|
case_4_apdu = h2b('84f2800218783fff80990f5585b1055010ea95094a26e4a8f1ef4b18e100')
|
|
|
|
class SCP03_Test_AES256_33(SCP03_Test, unittest.TestCase):
|
|
keyset = KEYSET_AES256
|
|
init_upd_cmd = h2b('805032000897b2055fe58599fd00')
|
|
init_upd_rsp = h2b('00000000000000000000320370a8439a22cedf045fa9f1903b2834f26e000002')
|
|
ext_auth_cmd = h2b('8482330010508a0fd959d2e547c6b33154a6be2057')
|
|
get_eid_cmd = h2b('84e29100187a5ef717eaf1e135ae92fe54429d0e465decda65f5fe5aea00')
|
|
get_eid_rsp = h2b('ea90dbfa648a67c5eb6abc57f8530b97d0cd5647c5e8732016b55203b078dd2ace7f8bc5d1c1cd99')
|
|
case_1_apdu = h2b('84f2200208bcc5c17275545d93')
|
|
case_2_apdu = h2b('84ca00660804806aba9d543bb600')
|
|
case_3_apdu = h2b('84f2200218717222491556ec81a45f49ce48be33320024801a1c4cb0e0')
|
|
case_4_apdu = h2b('84f2800218561f105bccd3a1642904b251ccc1228beb80a82370a8637000')
|
|
|
|
# FIXME:
|
|
# - for S8 and S16 mode
|
|
# FIXME: test auth with random (0x60) vs pseudo-random (0x70) challenge
|
|
|
|
|
|
class SCP03_KCV_Test(unittest.TestCase):
|
|
def test_kcv(self):
|
|
self.assertEqual(compute_kcv('aes', KEYSET_AES128.enc), h2b('C35280'))
|
|
self.assertEqual(compute_kcv('aes', KEYSET_AES128.mac), h2b('013808'))
|
|
self.assertEqual(compute_kcv('aes', KEYSET_AES128.dek), h2b('840DE5'))
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|