pySim/transport add support for T=1 protocol and fix APDU/TPDU layer conflicts

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
This commit is contained in:
Philipp Maier
2024-10-28 17:28:43 +01:00
parent f951c56449
commit 852eff54df
25 changed files with 606 additions and 144 deletions

View File

@@ -36,14 +36,14 @@ class SCP02_Auth_Test(unittest.TestCase):
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(), '805020000840A62C37FA6304F8')
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(), '805020000840A62C37FA6304F8')
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):
@@ -61,15 +61,32 @@ class SCP02_Test(unittest.TestCase):
ext_auth_cmd = self.scp02.gen_ext_auth_apdu()
def test_mac_command(self):
wrapped = self.scp02.wrap_cmd_apdu(h2b('80f28002024f00'))
self.assertEqual(b2h(wrapped).upper(), '84F280020A4F00B21AAFA3EB2D1672')
# 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('80E2910006BF3E035C015A')
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''
@@ -81,7 +98,7 @@ class SCP03_Test:
@property
def host_challenge(self) -> bytes:
return self.init_upd_cmd[5:]
return self.init_upd_cmd[5:-1]
@property
def kvn(self) -> int:
@@ -128,6 +145,21 @@ class SCP03_Test:
# 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'))
@@ -139,75 +171,111 @@ KEYSET_AES256 = GpCardKeyset(0x32, h2b('000102030405060708090a0b0c0d0e0f00010203
class SCP03_Test_AES128_11(SCP03_Test, unittest.TestCase):
keyset = KEYSET_AES128
init_upd_cmd = h2b('8050300008b13e5f938fc108c4')
init_upd_cmd = h2b('8050300008b13e5f938fc108c400')
init_upd_rsp = h2b('000000000000000000003003703eb51047495b249f66c484c1d2ef1948000002')
ext_auth_cmd = h2b('84821100107d5f5826a993ebc89eea24957fa0b3ce')
get_eid_cmd = h2b('84e291000ebf3e035c015a558d036518a28297')
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('80503000088e1552d0513c60f3')
init_upd_cmd = h2b('80503000088e1552d0513c60f300')
init_upd_rsp = h2b('0000000000000000000030037030760cd2c47c1dd395065fe5ead8a9d7000001')
ext_auth_cmd = h2b('8482030010fd4721a14d9b07003c451d2f8ae6bb21')
get_eid_cmd = h2b('84e2910018ca9c00f6713d79bc8baa642bdff51c3f6a4082d3bd9ad26c')
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('8050300008fdf38259a1e0de44')
init_upd_cmd = h2b('8050300008fdf38259a1e0de4400')
init_upd_rsp = h2b('000000000000000000003003703b1aca81e821f219081cdc01c26b372d000003')
ext_auth_cmd = h2b('84823300108c36f96bcc00724a4e13ad591d7da3f0')
get_eid_cmd = h2b('84e2910018267a85dfe4a98fca6fb0527e0dfecce4914e40401433c87f')
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('80503100087396430b768b085b')
init_upd_cmd = h2b('80503100087396430b768b085b00')
init_upd_rsp = h2b('000000000000000000003103708cfc23522ffdbf1e5df5542cac8fd866000003')
ext_auth_cmd = h2b('84821100102145ed30b146f5db252fb7e624cec244')
get_eid_cmd = h2b('84e291000ebf3e035c015aff42cf801d143944')
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('805031000869c65da8202bf19f')
init_upd_cmd = h2b('805031000869c65da8202bf19f00')
init_upd_rsp = h2b('00000000000000000000310370b570a67be38446717729d6dd3d2ec5b1000001')
ext_auth_cmd = h2b('848203001065df4f1a356a887905466516d9e5b7c1')
get_eid_cmd = h2b('84e2910018d2c6fb477c5d4afe4fd4d21f17eff10d3578ec1774a12a2d')
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('80503100089b3f2eef0e8c9374')
init_upd_cmd = h2b('80503100089b3f2eef0e8c937400')
init_upd_rsp = h2b('00000000000000000000310370f6bb305a15bae1a68f79fb08212fbed7000002')
ext_auth_cmd = h2b('84823300109100bc22d58b45b86a26365ce39ff3cf')
get_eid_cmd = h2b('84e29100188f7f946c84f70d17994bc6e8791251bb1bb1bf02cf8de589')
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('805032000811666d57866c6f54')
init_upd_cmd = h2b('805032000811666d57866c6f5400')
init_upd_rsp = h2b('0000000000000000000032037053ea8847efa7674e41498a4d66cf0dee000003')
ext_auth_cmd = h2b('84821100102f2ad190eff2fafc4908996d1cebd310')
get_eid_cmd = h2b('84e291000ebf3e035c015af4b680372542b59d')
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('8050320008c6066990fc426e1d')
init_upd_cmd = h2b('8050320008c6066990fc426e1d00')
init_upd_rsp = h2b('000000000000000000003203708682cd81bbd8919f2de3f2664581f118000001')
ext_auth_cmd = h2b('848203001077c493b632edadaf865a1e64acc07ce9')
get_eid_cmd = h2b('84e29100183ddaa60594963befaada3525b492ede23c2ab2c1ce3afe44')
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('805032000897b2055fe58599fd')
init_upd_cmd = h2b('805032000897b2055fe58599fd00')
init_upd_rsp = h2b('00000000000000000000320370a8439a22cedf045fa9f1903b2834f26e000002')
ext_auth_cmd = h2b('8482330010508a0fd959d2e547c6b33154a6be2057')
get_eid_cmd = h2b('84e29100187a5ef717eaf1e135ae92fe54429d0e465decda65f5fe5aea')
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