mirror of
https://gitea.osmocom.org/sim-card/pysim.git
synced 2026-11-06 23:43:02 +03:00
tests: move unittests into a sub directory
We currently mix the unit-tests with the shell script based integration tests. Let's put them into a dedicated sub directory. Related: OS#6531 Change-Id: I0978c5353d0d479a050bbb6e7ae5a63db5e08d24
This commit is contained in:
1 parent
8680698f97
commit
e0241037e7
14 files changed
+2
-2
No files matched your search
Executable
+91
@@ -0,0 +1,91 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import unittest
|
||||
from pySim.utils import h2b, b2h
|
||||
from pySim.construct import filter_dict
|
||||
from pySim.apdu import Apdu
|
||||
from pySim.apdu.ts_31_102 import UsimAuthenticateEven
|
||||
|
||||
class TestApdu(unittest.TestCase):
|
||||
def test_successful(self):
|
||||
apdu = Apdu('00a40400023f00', '9000')
|
||||
self.assertEqual(apdu.successful, True)
|
||||
apdu = Apdu('00a40400023f00', '6733')
|
||||
self.assertEqual(apdu.successful, False)
|
||||
|
||||
def test_successful_method(self):
|
||||
"""Test overloading of the success property with a custom method."""
|
||||
class SwApdu(Apdu):
|
||||
def _is_success(self):
|
||||
return False
|
||||
apdu = SwApdu('00a40400023f00', '9000')
|
||||
self.assertEqual(apdu.successful, False)
|
||||
|
||||
# TODO: Tests for TS 102 221 / 31.102 ApduCommands
|
||||
|
||||
class TestUsimAuth(unittest.TestCase):
|
||||
"""Test decoding of the rather complex USIM AUTHENTICATE command."""
|
||||
def test_2g(self):
|
||||
apdu = ('80880080' + '09' + '080001020304050607',
|
||||
'04a0a1a2a308b0b1b2b3b4b5b6b79000')
|
||||
res = {
|
||||
'cmd': {'p1': 0, 'p2': {'scope': 'df_adf_specific', 'authentication_context': 'gsm'},
|
||||
'body': {'rand': h2b('0001020304050607'), 'autn': None}},
|
||||
'rsp': {'body': {'sres': h2b('a0a1a2a3'), 'kc': h2b('b0b1b2b3b4b5b6b7')}}
|
||||
}
|
||||
u = UsimAuthenticateEven(apdu[0], apdu[1])
|
||||
d = filter_dict(u.to_dict())
|
||||
self.assertEqual(d, res)
|
||||
|
||||
def test_3g(self):
|
||||
apdu = ('80880081' + '12' + '080001020304050607081011121314151617',
|
||||
'DB' + '08' + 'a0a1a2a3a4a5a6a7' +
|
||||
'10' + 'b0b1b2b3b4b5b6b7b8b9babbbcbdbebf' +
|
||||
'10' + 'c0c1c2c3c4c5c6c7c8c9cacbcccdcecf' + '9000')
|
||||
res = {
|
||||
'cmd': {'p1': 0, 'p2': {'scope': 'df_adf_specific', 'authentication_context': 'umts'},
|
||||
'body': {'rand': h2b('0001020304050607'), 'autn': h2b('1011121314151617')}},
|
||||
'rsp': {'body': {'tag': 219,
|
||||
'body': {
|
||||
'res': h2b('a0a1a2a3a4a5a6a7'),
|
||||
'ck': h2b('b0b1b2b3b4b5b6b7b8b9babbbcbdbebf'),
|
||||
'ik': h2b('c0c1c2c3c4c5c6c7c8c9cacbcccdcecf'),
|
||||
'kc': None
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
u = UsimAuthenticateEven(apdu[0], apdu[1])
|
||||
d = filter_dict(u.to_dict())
|
||||
self.assertEqual(d, res)
|
||||
|
||||
def test_3g_sync(self):
|
||||
apdu = ('80880081' + '12' + '080001020304050607081011121314151617',
|
||||
'DC' + '08' + 'a0a1a2a3a4a5a6a7' + '9000')
|
||||
res = {
|
||||
'cmd': {'p1': 0, 'p2': {'scope': 'df_adf_specific', 'authentication_context': 'umts'},
|
||||
'body': {'rand': h2b('0001020304050607'), 'autn': h2b('1011121314151617')}},
|
||||
'rsp': {'body': {'tag': 220, 'body': {'auts': h2b('a0a1a2a3a4a5a6a7') }}}
|
||||
}
|
||||
u = UsimAuthenticateEven(apdu[0], apdu[1])
|
||||
d = filter_dict(u.to_dict())
|
||||
self.assertEqual(d, res)
|
||||
|
||||
def test_vgcs(self):
|
||||
apdu = ('80880082' + '0E' + '04' + '00010203' +
|
||||
'01' + '10' +
|
||||
'08' + '2021222324252627',
|
||||
'DB' + '10' + 'b0b1b2b3b4b5b6b7b8b9babbbcbdbebf' + '9000')
|
||||
res = {
|
||||
'cmd': {'p1': 0, 'p2': {'scope': 'df_adf_specific', 'authentication_context': 'vgcs_vbs'},
|
||||
'body': { 'vk_id': h2b('10'), 'vservice_id': h2b('00010203'), 'vstk_rand': h2b('2021222324252627')}},
|
||||
'rsp': {'body': {'vstk': h2b('b0b1b2b3b4b5b6b7b8b9babbbcbdbebf')}}
|
||||
}
|
||||
u = UsimAuthenticateEven(apdu[0], apdu[1])
|
||||
d = filter_dict(u.to_dict())
|
||||
self.assertEqual(d, res)
|
||||
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
@@ -0,0 +1,103 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import unittest
|
||||
from pySim.utils import b2h, h2b
|
||||
from pySim.construct import *
|
||||
from construct import FlagsEnum
|
||||
|
||||
tests = [
|
||||
( b'\x80', 0x80 ),
|
||||
( b'\x80\x01', 0x8001 ),
|
||||
( b'\x80\x00\x01', 0x800001 ),
|
||||
( b'\x80\x23\x42\x01', 0x80234201 ),
|
||||
]
|
||||
|
||||
class TestGreedyInt(unittest.TestCase):
|
||||
def test_GreedyInt_decoder(self):
|
||||
gi = GreedyInteger()
|
||||
for t in tests:
|
||||
self.assertEqual(gi.parse(t[0]), t[1])
|
||||
def test_GreedyInt_encoder(self):
|
||||
gi = GreedyInteger()
|
||||
for t in tests:
|
||||
self.assertEqual(t[0], gi.build(t[1]))
|
||||
pass
|
||||
|
||||
class TestUtils(unittest.TestCase):
|
||||
def test_filter_dict(self):
|
||||
inp = {'foo': 0xf00, '_bar' : 0xba5, 'baz': 0xba2 }
|
||||
out = {'foo': 0xf00, 'baz': 0xba2 }
|
||||
self.assertEqual(filter_dict(inp), out)
|
||||
|
||||
def test_filter_dict_nested(self):
|
||||
inp = {'foo': 0xf00, 'nest': {'_bar' : 0xba5}, 'baz': 0xba2 }
|
||||
out = {'foo': 0xf00, 'nest': {}, 'baz': 0xba2 }
|
||||
self.assertEqual(filter_dict(inp), out)
|
||||
|
||||
|
||||
class TestUcs2Adapter(unittest.TestCase):
|
||||
# the three examples from TS 102 221 Annex A
|
||||
EXAMPLE1 = b'\x80\x00\x30\x00\x31\x00\x32\x00\x33'
|
||||
EXAMPLE2 = b'\x81\x05\x13\x53\x95\xa6\xa6\xff\xff'
|
||||
EXAMPLE3 = b'\x82\x05\x05\x30\x2d\x82\xd3\x2d\x31'
|
||||
ad = Ucs2Adapter(GreedyBytes)
|
||||
|
||||
def test_example1_decode(self):
|
||||
dec = self.ad._decode(self.EXAMPLE1, None, None)
|
||||
self.assertEqual(dec, "0123")
|
||||
|
||||
def test_example2_decode(self):
|
||||
dec = self.ad._decode(self.EXAMPLE2, None, None)
|
||||
self.assertEqual(dec, "S\u0995\u09a6\u09a6\u09ff")
|
||||
|
||||
def test_example3_decode(self):
|
||||
dec = self.ad._decode(self.EXAMPLE3, None, None)
|
||||
self.assertEqual(dec, "-\u0532\u0583-1")
|
||||
|
||||
testdata = [
|
||||
# variant 2 with only GSM alphabet characters
|
||||
( "mahlzeit", '8108006d61686c7a656974' ),
|
||||
# variant 2 with mixed GSM alphabet + UCS2
|
||||
( "mahlzeit\u099523", '810b136d61686c7a656974953233' ),
|
||||
# variant 3 due to codepoint exceeding 8 bit
|
||||
( "mahl\u8023zeit", '820980236d61686c807a656974' ),
|
||||
# variant 1 as there is no common codepoint pointer / prefix
|
||||
( "\u3000\u2000\u1000", '80300020001000' ),
|
||||
]
|
||||
|
||||
def test_data_decode(self):
|
||||
for string, encoded_hex in self.testdata:
|
||||
encoded = h2b(encoded_hex)
|
||||
dec = self.ad._decode(encoded, None, None)
|
||||
self.assertEqual(dec, string)
|
||||
|
||||
def test_data_encode(self):
|
||||
for string, encoded_hex in self.testdata:
|
||||
encoded = h2b(encoded_hex)
|
||||
re_enc = self.ad._encode(string, None, None)
|
||||
self.assertEqual(encoded, re_enc)
|
||||
|
||||
class TestTrailerAdapter(unittest.TestCase):
|
||||
Privileges = FlagsEnum(StripTrailerAdapter(GreedyBytes, 3), security_domain=0x800000,
|
||||
dap_verification=0x400000,
|
||||
delegated_management=0x200000, card_lock=0x100000,
|
||||
card_terminate=0x080000, card_reset=0x040000,
|
||||
cvm_management=0x020000, mandated_dap_verification=0x010000,
|
||||
trusted_path=0x8000, authorized_management=0x4000,
|
||||
token_management=0x2000, global_delete=0x1000,
|
||||
global_lock=0x0800, global_registry=0x0400,
|
||||
final_application=0x0200, global_service=0x0100,
|
||||
receipt_generation=0x80, ciphered_load_file_data_block=0x40,
|
||||
contactless_activation=0x20, contactless_self_activation=0x10)
|
||||
examples = ['00', '80', '8040', '400010']
|
||||
def test_examples(self):
|
||||
for e in self.examples:
|
||||
dec = self.Privileges.parse(h2b(e))
|
||||
reenc = self.Privileges.build(dec)
|
||||
self.assertEqual(e, b2h(reenc))
|
||||
|
||||
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
Executable
+64
@@ -0,0 +1,64 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
# (C) 2023 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
|
||||
import base64
|
||||
|
||||
from pySim.utils import b2h, h2b
|
||||
from pySim.esim.bsp import *
|
||||
import pySim.esim.rsp as rsp
|
||||
from pySim.esim import ActivationCode
|
||||
|
||||
from cryptography.hazmat.primitives.asymmetric import ec
|
||||
|
||||
class TestActivationCode(unittest.TestCase):
|
||||
def test_de_encode(self):
|
||||
STRS = ['1$SMDP.GSMA.COM$04386-AGYFT-A74Y8-3F815',
|
||||
'1$SMDP.GSMA.COM$04386-AGYFT-A74Y8-3F815$$1',
|
||||
'1$SMDP.GSMA.COM$04386-AGYFT-A74Y8-3F815$1.3.6.1.4.1.31746$1',
|
||||
'1$SMDP.GSMA.COM$04386-AGYFT-A74Y8-3F815$1.3.6.1.4.1.31746',
|
||||
'1$SMDP.GSMA.COM$$1.3.6.1.4.1.31746']
|
||||
for s in STRS:
|
||||
ac = ActivationCode.from_string(s)
|
||||
self.assertEqual(s, ac.to_string())
|
||||
|
||||
|
||||
class TestECKA(unittest.TestCase):
|
||||
def test_mode51(self):
|
||||
curve = ec.SECP256R1()
|
||||
euicc_otpk_der = h2b('0400f7b8d71403f21d84b00cd9e561178d737d3f4d065e62fee279271298dd4f074794ab791b9939d4461296efe388aa26731064263af988b7d2c4d77da44801b5')
|
||||
smdp_otpk_der = h2b('04a27e2bdbd94dcf67d4c9ae5cb149d9d0f093be7a16dc41ec9db0318e4db72d09234a7d7631979a5d150eec40afe17ce41673df9d2f2e4246d60051c74eba7964')
|
||||
smdp_otsk_bytes = h2b('fb68a38ccedb69e15cbe03c256228998ac398587e5dc7117f948145c839d61a4')
|
||||
expected_shared_secret = h2b('c9a993dd4879a8f7161f2085410edd4f9652f1df37be097ba96ba2ca6be528fe')
|
||||
|
||||
euicc_otpk = ec.EllipticCurvePublicKey.from_encoded_point(curve, bytes(euicc_otpk_der))
|
||||
smdp_otpk = ec.EllipticCurvePublicKey.from_encoded_point(curve, bytes(smdp_otpk_der))
|
||||
smdp_otsk = ec.derive_private_key(int.from_bytes(smdp_otsk_bytes, 'big'), curve)
|
||||
|
||||
shared_secret = smdp_otsk.exchange(ec.ECDH(), euicc_otpk)
|
||||
|
||||
self.assertEqual(shared_secret, expected_shared_secret)
|
||||
|
||||
|
||||
class TestBSPdecode(unittest.TestCase):
|
||||
bpp_b64 = "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 truncated
|
||||
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
Executable
+98
@@ -0,0 +1,98 @@
|
||||
#!/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
|
||||
import base64
|
||||
|
||||
from pySim.utils import b2h, h2b
|
||||
from pySim.esim.bsp import *
|
||||
import pySim.esim.rsp as rsp
|
||||
|
||||
class BSP_Test(unittest.TestCase):
|
||||
shared_secret = h2b('8902dca391bbb22570fe60c176076246f568b1941265dff1d729e63039658089')
|
||||
eid_bin = h2b('89049032123451234512345678901335')
|
||||
def test_kdf(self):
|
||||
s_enc, s_mac, icv = bsp_key_derivation(self.shared_secret, 0x88, 16, b'\x80'*8, self.eid_bin)
|
||||
self.assertEqual(s_enc, h2b('d782d575b22a38334556a3d9a1e2ae6b'))
|
||||
self.assertEqual(s_mac, h2b('a35addc192ae8f934e2932116c7e89e7'))
|
||||
self.assertEqual(icv, h2b('734bc93ddb10c71c5ad13d420dc08b5b'))
|
||||
|
||||
class BSP_Test_mode51(unittest.TestCase):
|
||||
"""This test was created using hex-dumps from a log/trace of a 3rd party SM-DP+. We therefore use these
|
||||
test vectors to verify our implementation is in agreement with that other implementation."""
|
||||
shared_secret = h2b('c9a993dd4879a8f7161f2085410edd4f9652f1df37be097ba96ba2ca6be528fe')
|
||||
eid_bin = h2b('89049032123451234512345678901235')
|
||||
|
||||
def test_kdf(self):
|
||||
s_enc, s_mac, icv = bsp_key_derivation(self.shared_secret, 0x88, 16, b'\x80'*8, self.eid_bin)
|
||||
self.assertEqual(s_enc, h2b('472f5bfadd97f21d34f3ce9b51b92751'))
|
||||
self.assertEqual(s_mac, h2b('9ec07f5b36a13e12a991d66e294e6242'))
|
||||
self.assertEqual(icv, h2b('406d507b448a699e7a36a38494debbde'))
|
||||
|
||||
def test_ciphering(self):
|
||||
bi = BspInstance(h2b('472f5bfadd97f21d34f3ce9b51b92751'), h2b('9ec07f5b36a13e12a991d66e294e6242'), h2b('406d507b448a699e7a36a38494debbde'))
|
||||
output = bi.encrypt_and_mac(0x87, h2b('bf2400'))
|
||||
self.assertEqual(output[0], h2b('8718f6fe031e4b9cfe87c8e1e62f3fde85c49412d7722a6a2d89'))
|
||||
|
||||
output = bi.mac_only(0x88, h2b('bf252d5a0a98001032547698103214910947534d415f54455354921147534d415f544553545f50524f46494c45950100'))
|
||||
self.assertEqual(output[0], h2b('8838bf252d5a0a98001032547698103214910947534d415f54455354921147534d415f544553545f50524f46494c459501008a62cc9adbb5ccbc'))
|
||||
|
||||
output = bi.encrypt_and_mac(0x87, h2b('bf26368010000102030405060708090a0b0c0d0e0f8110010102030405060708090a0b0c0d0e0f8210020102030405060708090a0b0c0d0e0f'))
|
||||
self.assertEqual(output[0], h2b('8748a14c800a001992351cd46ad2945654674369701d82b7b46567652bc8fbed234939fc57fba748015525fd6c651e9d3d1330652d42a0cfad950e912122af4ec5362d3c0bc535729c40'))
|
||||
|
||||
# new key material after replaceSessionKeys
|
||||
bi = BspInstance(b'\x01\x01\x02\x03\x04\x05\x06\x07\x08\t\n\x0b\x0c\r\x0e\x0f',
|
||||
b'\x02\x01\x02\x03\x04\x05\x06\x07\x08\t\n\x0b\x0c\r\x0e\x0f',
|
||||
b'\x00\x01\x02\x03\x04\x05\x06\x07\x08\t\n\x0b\x0c\r\x0e\x0f')
|
||||
|
||||
segment0 = h2b('a048800102810101821a53494d616c6c69616e63652053616d706c652050726f66696c65830a89000123456789012341a506810084008b00a610060667810f010201060667810f010204b08201f8a0058000810101810667810f010201a207a105c60301020aa305a1038b010fa40c830a98001032547698103214a527a109820442210026800198831a61184f10a0000000871002ff33ff01890000010050045553494da682019ea10a8204422100258002022b831b8001019000800102a406830101950108800158a40683010a95010882010a8316800101a40683010195010880015aa40683010a95010882010f830b80015ba40683010a95010882011a830a800101900080015a970082011b8316800103a406830101950108800158a40683010a95010882010f8316800111a40683010195010880014aa40683010a95010882010f8321800103a406830101950108800158a40683010a950108840132a4068301019501088201048321800101a406830101950108800102a406830181950108800158a40683010a950108820104831b800101900080011aa406830101950108800140a40683010a95010882010a8310800101900080015aa40683010a95010882011583158001019000800118a40683010a95010880014297008201108310800101a40683010195010880015a97008201158316800113a406830101950108800148a40683010a95010882010f830b80015ea40683010a95010882011a83258001019000800102a010a406830101950108a406830102950108800158a40683010a950108a33fa0058000810102a13630118001018108303030303030303082020099300d800102810831323334353637383012800200818108313233343536373882020088a241a0058000810103a138a0363010800101810831323334ffffffff8201013010800102810830303030ffffffff820102301080010a810835363738ffffffff830101a182029ea0058000810104a18202933082028f62228202782183027ff18410a0000000871002ff33ff0189000001008b010ac60301810a62118202412183026f078b01028001098801388109082943019134876765621482044221002583026f068b010a8801b8c7022f06621a8202412183026f088b0105800121880140a507c00180c10207ff621a8202412183026f098b0105800121880148a507c00180c10207ff62168202412183026f318b0102800101880190a503c1010a62118202412183026f388b010280010e880120810d0a2e178ce73204000000000000621982044221001a83026f3b8b0108800202088800a504c10200ff62198204422100b083026f3c8b0105800206e08800a504c10200ff621282044221002683026f428b0105800126')
|
||||
output = bi.encrypt_and_mac_one(0x86, segment0)
|
||||
self.assertEqual(output, h2b('868203f80fd36b066b43e53906b33263c4141d18036fcac7bde47faed79e76514d39f1f405d9785ff04badf379a96bcf4685eb861239da34eeb213d0cd1e0d85e96e36097a52f600907e08b6f01232eb3792a00cc6b3288cc832c7f30357edfe0818d1f39ad8d55b5bd5d9c65917d0d16ee7e1421a44453714d66209bf441425bf6d153a85ba7e7406d58a4c46fb930bb14eefd6a853434619805429cbbb003bd5a56e7bceaf4666eb352ad46f47ea572c6ad311683803db3bec4d783583858f2ecfbaec3ed780fb3e3a645b1bf5cfd1047e5862c4a8877382dabd6aef6fa72ec6378ce7b21502d3267514b29bd589703ee7bd5b1e6d868b6b7c55006bc32406a924f44bfdb6c801d490450a54633bbd66bb0ece8b3a9be09433c537f3e3b2b8e45833365a94479b1895e9ba13387fca116565c267b6bc1274c82510b21ac9fa77574412351468fd1eac8928c9494e5eb8a909d9372476c62e8a2b556b557a79cdb47503ba5d9fd86d1962c23b3289f37df9a05668957df34c37806359af1d8dfecedf9bbf4888be9753f2b449ce4e5cde510572f9be4fb506f329a848c4583ac9ac710e53d512ad504f2e3769c2911c34f84ad0622c5428446d1e9c59bb6b2029f231b05ef45d3ec60fceff121ba684a023037b753f855e1067b8ad02783c04eb81ba47fcec0947bbc9661d90c6f00c0f4e6f9c22e0b25905379f8b265b436a4a74253a64c5734f956d134cf5b6c0671125b20b735f405fee1fc1941ed141734fa6856e898dc655fb91b045b3797d14b97a68ed3480ec137565d6c4d007f95ee705d452d0344ce9e9bbd0712f399ef8604f3f472b403ea64f00c969fd30a012cb8be049c54556994117fc5f6043930107774910be5f47c780427de063a56d45400a814c86dfcc2465262baf273a6ca89e77bb8c73efe2d22d975516b80c648404b10d3f329776a21251bedf445ade24b656e8635f0d7fe39772d942d9432766efb5152c6e990123c52744f1c402962a381ff2aaddaa1bd522a9b65a229f9adcd4099475b3f9ba45161ee35206365181e1d69d263d8d27444a7c3a2d7ddbb398ac5affea28cb0373057dad7741e52adb95822fee5157ce3c0ad978c5231a219ca0726d50eeb0c69d579094a54820194aeef13a365d85c52257f51cb65c567e0cdf1ce38b80eaa4d131ab7086e303c5728cf41a25df954d60ac12400d2a2990e67dbeff866736937beb8fce526be1dd5dc5d77d00b8783b34691a3e9bda7e697eec4cfa70b25914795af23ec6d258530f71402b9230947dc99f9140b16a54ba1291f54ef736de6711c1ae074b627dfc2fc5d5250f812b7ecd1d0f5020a3acf20ad5759cd9e761ce21882977f7db66aaba2d8f5190f6f23996af14b670ccac066c93af06a41f19d9ee712ce13fbe73b99e55401b208b991c8ced12d34c'))
|
||||
|
||||
segment1 = h2b('880062158202412183026f438b01058001028800a503c0018062128202412183026f468b036f060a8001118800810c0253494d616c6c69616e636562118202412183026f568b0108800101880128810100621b8202412183026f5b8b0105800106880178a508c00180c203f0000062168202412183026f5c8b0102800103880180a503c0018062168202412183026f738b010580010e880160a503c00180020107810700f1100000ff0162118202412183026f788b01028001028801308102004062118202412183026f7b8b010580010c88016862168202412183026f7e8b010580010b880158a503c0018002010781040000ff0162168202412183026fad8b010a800104880118a503c10100020103810102621382044221000483026fb78b010a800104880108810419f1ff0162158202412183026fc48b01058001808800a503c0018062168202412183026fe38b01058001128801f0a503c0018002010f810300000162168202412183026fe48b01058001508801c0a503c00180a225a0058000810105a11ca01a301880020081810831323334ffffffff82020081830101840122a43aa0058000810106a131a12f8001018101018210000102030405060708090a0b0c0d0e0f83100102030405060708090a0b0c0d0e0f008603010203a681bba0058000810107a1444f07a00000015153504f08a0000001515350414f08a000000151000000820382dc0083010fc90a810280008201f08701f0ea11800f0100000100000002011203b2010000a26c3022950138820101830101301730158001808610112233445566778899aabbccddeeff103022950134820102830101301730158001808610112233445566778899aabbccddeeff1030229501c8820103830101301730158001808610112233445566778899aabbccddeeff10a681c0a0058000810108a1494f07a00000015153504f08a0000001515350414f10a00000055910100102736456616c7565820380800083010fc907810280008201f0ea11800f01000001000000020112036c756500a26c30229501388201018301013017301580018086108811223344556677881122334455667730229501348201028301013017301580018086108811223344556677881122334455667730229501c882010383010130173015800180861088112233445566778811223344556677a8820263a0058000810109a18202184f08a000000559101001c482020a01002edecaffed020204000108a0000005591010011b636f6d2f67736d612f65756963632f746573742f6170706c657431020021002e0021000f003b002a00210066000a000e0000008a040f00000000000004010004003b04030107a0000000620101000110a0000000090005ffffffff')
|
||||
output = bi.encrypt_and_mac_one(0x86, segment1)
|
||||
self.assertEqual(output, h2b('868203f8648e034ae0dc4ce022ee1e60b130dda95e13b21b0da3de7677677f47900c1beb3637b8aa35f3a9e096c0285ffe3e931983df900b36b7e6bc4b9af14b0ee3d49637eb2d4cff314b5d00789a751dfd9554651fb2b7c66ad4e22a794d5b88cb71ccf4c05d53abeba8bd3b0c8209346f014cbdee62be4878e3fea09a96007135a6c584aa843c48972842bdbece1c439723021b3f0d535d557995beedbd2b56f416148df90cb1a4d2fa26288801d56a2cbb0a404f2fd9a73042d7a3486bfb7256c1d274aae5b7ec24e8eba28b7dce69edc44189b24186b98397b4a74831f8ab46e8e46a2ed3077d4924f5d3f6e4c1de5ddffd194e7f0f97d94ea2801d1364835c9871bae6539e3e1355c5970711d845864f04d9c1dccac0d4068dcf4664e9976509fe43fec6beb3ddc96839aba6d89bb1593c5b6ebbc32fff39c4a5bb3e5c9df6a1abc05818dbd5149733381e69521066e1bbd648eb19b00602767e90beaeb3b3b92679940a603c0500e37892d7b4fa44355c3deec8af207f89d04f83cd88603e9cb9c96f74643816e87af85a8a9d0283cdf535d1fbaefb930fd4a0dba2ae30ee9d2d9e2a31827a012a6380af42ac87f3bfd7079ddd8fa27d2299fb4d5879e9a17a5062e13cab4f7bed22ed54932fa53d630bca8592f957a7ed148e9d4f28075c2565a550694b876091a1181ba512e70fde4f28ae6968a18d721396c0fee9cd7744dee90bf85f5adddb3417b3ede9ea3cfd5eae2d820b17600ce3b95f6df38a5bc39302c5155c3f241ddfc7cee527af7f6a67868a577c39e76e26c4ed5d6aca031a97c280da27ae8de20e57a1dbab40a31e96e054f9a6f50578fd00156e37b70eead71af3258075c1ba84282aea462553504a868443b301ed99dcd5f414b720ef67cf5c4d16f1f7b9df741c2343246dcb717f3fa62b633539bdcc0082d161499caad8d097be78133dafd19777559f77c6d7a8f61323ff660613aa47cee26a4f7515204eee3c7eaa00eb55529b0ddae3436ec679fc591fe2063de94db00b5d0f041beeb80a91f108f7cf4b3b1344b0fbb437630ee437b4c7744c54009a59a9099681f3a3fa386f294c0eb4562581202a369772833efdc6e840695352de3864671e7fb0fd081ec162a2a62ea5b8a9da837f3920b4196fbe2ec912ade440537ae4a07dfc115c9c030539f278e0801bda4f15298ba50e329b18992af8b899686ec97175509d4a217d2eba8feef5f5732fc7be86370f7723896b784bd45517af86a7521e952b6be924d91a5190e3c2c65ce8924df43ddb25c529dde324a722a156df459f4b38bb062975fad9fadd27f6425e422b1abd9a7259f0bd712a486e1aa25ca848cf65c5fb888bf61ae136b68cf55cb643c198537cd83df0dbb842c5f4982ca3088cc2d8e867c3049a84b515ec39b0b774a8482099327006acff'))
|
||||
|
||||
|
||||
class TestBSPdecode(unittest.TestCase):
|
||||
"""This test verifies whether some fully encoded/encrypted/MACed bound profile package can be properly
|
||||
decrypted and verified using our BSP code base."""
|
||||
bpp_b64 = "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 truncated
|
||||
|
||||
def test_decrypt_and_mac_verify(self):
|
||||
bpp_bin = base64.b64decode(self.bpp_b64)
|
||||
iscr = rsp.asn1.decode('BoundProfilePackage', bpp_bin)
|
||||
|
||||
# BSP segment for ConfigureISDP using S-ENC/S-MAC
|
||||
bsp = BspInstance(h2b('f11b921c302efb1df6bf218bf2de0dd8'), h2b('8cd4d3a761eeac108641d5f4531a137e'), h2b('ee3140eafd692b68eff40119b363bc6c'))
|
||||
plain_cfg_isdp = bsp.demac_and_decrypt(iscr['firstSequenceOf87'])
|
||||
self.assertEqual(plain_cfg_isdp, h2b('bf2400'))
|
||||
|
||||
# BSP segment for StoreMetadata using /S-MAC
|
||||
plain_smd = bsp.demac_only(iscr['sequenceOf88'])
|
||||
self.assertEqual(plain_smd, h2b('bf25285a0a89000123456789012341910a4f736d6f636f6d53504e920e4f736d6f636f6d50726f66696c65'))
|
||||
|
||||
# BSP payload using PPK-ENC/PPK-MAC
|
||||
bsp_ppk = BspInstance(h2b('00000000000000000000000000000000'), h2b('11111111111111111111111111111111'), h2b('22222222222222222222222222222222'))
|
||||
plain_upp = bsp_ppk.demac_and_decrypt(iscr['sequenceOf86'])
|
||||
original_upp = b'oIGdgAECgQEDgh9HU01BIEdlbmVyaWMgZVVJQ0MgVGVzdCBQcm9maWxlgwqJAAEjRWeJASNBpQ6BAIIAgwCRAJUAlgCXAKZYBgZngQ8BAgEGBmeBDwECAwYGZ4EPAQIEBgZngQ8BAgUGBmeBDwECBwYGZ4EPAQIIBgZngQ8BAgkGBmeBDwECCgYGZ4EPAQILBgZngQ8BAg0GBmeBDwECDrCCAvegBYAAgQEEgQZngQ8BAgGiC6EJiwMvBgHGAgEKoxyhGoICQSGDAi8FiwMvBgSAAQalCMABQMEDZW7/pCChEoICQSGDAi/iiwMvBgOlA8ABQIMKmAAQMlR2mBAyFKVroReCBEIhACGDAi8AiwMvBgKAAYSlA8ABQIMWYRRPDKAAAACHEAL/Sf8FiVAEVVNJTYIBC4MWYRRPDKAAAACHEAT/Sf8FiVAESVNJTYIBC4MaYRhPEKAAAANDEALzEP//iQIAAP9QBENTSU2mggIKoRiCBEIhAC6DAi8GiwMvBgKAAgKypQPAAUCDFoABW6QGgwEKlQEIhAHUpAaDAQqVAQiCARiDG4ABAZAAgAFapAaDAQqVAQiEAdSkBoMBCpUBCIIBE4MQgAEBkACAARikBoMBCpUBCIIBHoMmgAEBkACAAQKkBoMBAZUBCIQB1KQGgwEKlQEIgAFYpAaDAQqVAQiCAQiDfYABA6QGgwEBlQEIgAEYpAaDAYGVAQiAAUCkBoMBCpUBCIQB1KQGgwEKlQEI//+AAQGkBoMBAZUBCIABAqQGgwGBlQEIgAFYpAaDAQqVAQiEAdSkBoMBCpUBCP//gAEDpAaDAQGVAQiAAVikBoMBCpUBCIQB1KQGgwEKlQEIggENgyGAAQGkBoMBAZUBCIABWqQGgwEKlQEIhAHUpAaDAQqVAQiCAQ2DFoABAaQGgwEBlQEIgAFSpAaDAQqVAQiCARiDIYABAaQGgwEBlQEIgAFapAaDAQqVAQiEAdSkBoMBCpUBCIIBDYMhgAEDpAaDAQGVAQiEAdSkBoMBCpUBCIABWKQGgwEKlQEIggENgxaAAQGkBoMBAZUBCIABAqQGgwEKlQEIggEYg0mAAQGkBoMBAZUBCIABAqQGgwGBlQEIhAHUpAaDAQqVAQiAAVikBoMBCpUBCP//gAEDkACAAVikBoMBCpUBCIQB1KQGgwEKlQEIpx6hFYICQSGDAi8IiwMvBgKIAUClA8ABQIMFPDwAAACjKKAFgACBAQWhHzANgAEBgQgxMTExMTExMTAOgAIAgYEIMjIyMjIyMjKiTKAFgACBAQKhQ6BBMBOAAQGBCDAwMDD/////ggEBgwEGMBSAAQqBCDU1NTU1NTU1gwEDhAIAqjAUgAELgQg2NjY2NjY2NoMBA4QCAKqyggIcoAWAAIEBB4EGZ4EPAQIDohChDoMCfxCLAy8GAcYDgQEKox+hGoIEQiEAFIMCbwaLAy8GAoABFIgBuKUDwAFAgwH/pRuhGYICQSGDAm9UiwMvBgGAARKIAKUFwQOFAP+oM6EZggRCIQAWgwJv5YsDLwYHgAEWiAClA8ABQIMWgBR0ZWw6KzExMjIzMzQ0NTU2Njc3OKkQoQ6DAl9QiwMvBgHGA4EBCqofoR2CBEIhAAqDAk8giwMvBgqAAQqIAKUHwAFAwQIA/60doRiCAkEhgwJPAYsDLwYKgAICAIgApQPAAUCDAf+vEKEOgwJfOosDLwYBxgOBAQqwZaFjggRCIQBkgwJPMIsDLwYIgAFkiAClTcFLqCPAA086CsEDTxUFxQNPCQHGA09MC8oDT1EJwwNPGQTJA08WBqkPxANPEQLEA08TB8oDTxQIqhLCA08SA8sDTz0MxwJPS8gCT03/tBmhF4ICQSGDAk8iiwMvBgeIAKUGwAFAwgEAtRmhF4ICQSGDAk8jiwMvBgeIAKUGwAHAwgEAthqhFIICQSGDAk8kiwMvBgeIAKUDwAHAgwIAAr8kEKEOgwJfPIsDLwYBxgOBAQq/JSahJIICQSGDAk8giwMvBgqAARKIAQilD8ABQMEKAAkBAAEBAQAB/78mLKEqggJBIYMCTyGLAy8GCoABEogBEKUVwAFAwRAADwEAAQEBAwcIAgwAPgD/oiKgBYAAgQEDoRmgFzAVgAIAgYEIOTk5Of////+CAgCBgwEDoYIDFqAFgACBARehggMLMIIDB4AAYhqCBEIhAHyDAi/7iwMvBg6AAgTYiAClA8ABQIACfxBiFoIERiEAGoMCb0SLAy8GB4ABgogApQCABH8QXzpiGoIEQiEAAoMCTwmLAy8GB4ABCogBCKUDwgEAYheCBEIhABGDAk8RiwMvBgeAAVWIARClAGIeggRCIQANgwJPEosDLwYHgAFBiAEYpQfAAUDBAgD/YhqCBEIhABGDAk8TiwMvBgeAAVWIATilA8ABQGIXggRCIQAogwJPFIsDLwYHgAHIiAFApQBiGoIEQiEAA4MCTxWLAy8GB4ABD4gBKKUDwAFAYh6CBEIhAAKDAk8WiwMvBgeAARSIATClB8ABQMICAAFiGoIEQiEAFIMCTxmLAy8GB4AByIgBIKUDwAFAYheCBEIhAByDAk86iwMvBgeAAYyIAVClAIERVGVzdG5yLjH//waRlJghQ/ECAQuBEVRlc3Ruci4y//8GkZSYIUPyYhqCBEIhAA+DAk89iwMvBgeAAZaIAWClA8ABQGIZggRCIQAKgwJPS4sDLwYHgAFkiAClA8ABQGIdggRCIQAKgwJPTIsDLwYHgAFkiAFYpQbAAUDCAQBiGYIEQiEAFIMCT02LAy8GB4AByIgApQPAAUBiGoIEQiEAKIMCT1GLAy8GB4AByIgBSKUDwAFAgAJ/EGISggJ4IYMCXz6LAy8GAcYDgQEKgAR/EF8+YhWCAkEhgwJPAYsDLwYKgAECpQPAAUCBAgAAgABiEYICeCGDAn9miwMvBgHGAgEKgAJ/ZmIRggJ4IYMCX0CLAy8GAcYCAQqABH9mX0BiF4ICQSGDAk9AiwMvBgKAAQGIAKUDwAFAgQEAYheCAkEhgwJPQYsDLwYCgAEgiAClA8ABQIEgBgE8HjweAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABiGoICQSGDAk9CiwMvBgKAAQaIAKUGwAFAwgEAYhqCAkEhgwJPQ4sDLwYEgAEgiAClBsABQMIBAGIXggJBIYMCT0SLAy8GBIABAYgApQPAAUCBAQCzggScoAWAAIEBCIEGZ4EPAQIEoh6hHIMCf9CEDKAAAACHEAL/Sf8FiYsDLwYBxgOBAQqjHKEPggJBIYMCbweLA28GCqUAgwkICRAQEDJUBjakggHpoRWCBEIhADaDAm8GiwNvBgiAAgMqpQCDIYABAaQGgwEBlQEIgAECpAaDAYGVAQiAAVikBoMBCpUBCIIBFYMWgAEDpAaDAQGVAQiAAVikBoMBCpUBCIIBIIMWgAEBpAaDAQGVAQiAAVqkBoMBCpUBCIIBIIMhgAEBpAaDAQGVAQiAAVqkBoMBCpUBCIQB1KQGgwEKlQEIggEVgyaAAQGQAIABAqQGgwEBlQEIhAHUpAaDAQqVAQiAAVikBoMBCpUBCIIBEIMhgAEDpAaDAQGVAQiEAdSkBoMBCpUBCIABWKQGgwEKlQEIggEVgxCAAQGQAIABWqQGgwEKlQEIggEmgxuAAQGQAIABWqQGgwEKlQEIhAHUpAaDAQqVAQiCARuDLIABAaQGgwEBlQEIgAECpAaDAYGVAQiEAdSkBoMBCpUBCIABWKQGgwEKlQEIggEKgxaAAQGkBoMBAZUBCIABGqQGgwEKlQEIggEgg0GAAQGkBoMBAZUBCIABWKQGgwEKlQEIhAEypAaDAQGVAQiAAQKgEKQGgwEBlQEIpAaDAYGVAQiAAQOkBoMBCpUBCIIBK4MWgAFbpAaDAQqVAQiEAdSkBoMBCpUBCIIBIIMbgAEDkACEAdSkBoMBCpUBCIABWKQGgwEKlQEIpRihFoICQSGDAm8IiwNvBgKlB8ABgMECB/+mGKEWggJBIYMCbwmLA28GAqUHwAGAwQIH/6cUoQ+CAkEhgwJvMYsDbwYDpQCDAQCoJ6ESggJBIYMCbziLA28GBIABEaUAgxGe/78d/z4Ag0EDEAEEAEBuAakYoRaCBEIhAByDAm87iwNvBgGAAYyIAKUAqh2hG4IEQiEAsIMCbzyLA28GAoACBuCIAKUEwQIA/6sYoRaCBEIhACqDAm9CiwNvBgKAAVSIAKUArBOhEYICQSGDAm9DiwNvBgKIAKUArSChHoICQSGDAm9GiwNvBgeIAKUNwQsBR1NNQRFURVNU/64UoQ+CAkEhgwJvVosDbwYBpQCDAQCvHKESggJBIYMCb1uLA28GAqUDwAGAgwbwAADwAACwFKESggJBIYMCb1yLA28GA6UDwgH/sSShEoICQSGDAm9ziwNvBgKlA8ABgIMO/////////0L2GP/+/wGyFaEPggJBIYMCb3iLA28GA6UAgwIAAbMRoQ+CAkEhgwJve4sDbwYGpQC0IaESggJBIYMCb36LA28GAqUDwAGAgwv/////QvYY//7/AbUXoQ+CAkEhgwJvrYsDbwYHpQCDBIAAAAK2NqEUggRCIQAOgwJvt4sDbwYIgAEcpQCDDhHy/0V1cm8gRW1lcv8Agw4Z8f9FbWVyZ2VuY3n/ALcZoReCAkEhgwJvxIsDbwYCgAEyiAClA8ABgLgfoRKCAkEhgwJv44sDbwYCpQPAAcCCAQyDBgAAAAAAAbkZoReCBEIhADaDAm/kiwNvBgKAATalA8ABwLSCCiugBYAAgQEJgQZngQ8BAgWiEaEPggJBIYMCbwWLA28GBaUAoxmhF4ICQSGDAm83iwNvBgmIAKUGwAFAwgEApB+hHYIERiEAA4MCbzmLA28GC4ABD4gB4KUGwAGAwgEApRqhFIICQSGDAm8+iwNvBgSAAQKIAKUAgwL//6YaoRLine truncated
|
||||
self.assertEqual(plain_upp, base64.b64decode(original_upp))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
Executable
+94
@@ -0,0 +1,94 @@
|
||||
#!/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
|
||||
import copy
|
||||
|
||||
from pySim.utils import h2b, b2h
|
||||
from pySim.esim.saip import *
|
||||
from pySim.esim.saip.personalization import *
|
||||
from pprint import pprint as pp
|
||||
|
||||
|
||||
class SaipTest(unittest.TestCase):
|
||||
with open('smdpp-data/upp/TS48v2_SAIP2.3_NoBERTLV.der', 'rb') as f:
|
||||
per_input = f.read()
|
||||
pes = ProfileElementSequence.from_der(per_input)
|
||||
expected_pet_list = ['header', 'mf', 'pukCodes', 'pinCodes', 'telecom', 'pinCodes', 'genericFileManagement', 'usim', 'opt-usim', 'pinCodes', 'akaParameter', 'gsm-access', 'df-5gs', 'df-saip','csim', 'opt-csim', 'pinCodes', 'cdmaParameter', 'isim', 'opt-isim', 'pinCodes', 'akaParameter', 'genericFileManagement', 'genericFileManagement', 'securityDomain', 'rfm', 'rfm', 'rfm', 'rfm', 'end']
|
||||
|
||||
def test_reencode_sequence(self):
|
||||
"""Test that we can decode and re-encode the entire DER encoded UPP."""
|
||||
reencoded_der = self.pes.to_der()
|
||||
self.assertEqual(reencoded_der, self.per_input)
|
||||
|
||||
def test_reencode_pe(self):
|
||||
"""Test that we can decode and re-encode reach individual ProfileElement."""
|
||||
remainder = self.per_input
|
||||
while len(remainder):
|
||||
first_tlv, remainder = bertlv_first_segment(remainder)
|
||||
pe = ProfileElement.from_der(first_tlv)
|
||||
with self.subTest(pe.type):
|
||||
reenc_tlv = pe.to_der()
|
||||
self.assertEqual(reenc_tlv, first_tlv)
|
||||
|
||||
|
||||
def test_sequence_helpers(self):
|
||||
"""Verify that the convenience helpers worked as expected."""
|
||||
self.assertEqual([x.type for x in self.pes.pe_list], self.expected_pet_list)
|
||||
self.assertEqual(len(self.pes.pes_by_naa), 4)
|
||||
|
||||
def test_personalization(self):
|
||||
"""Test some of the personalization operations."""
|
||||
pes = copy.deepcopy(self.pes)
|
||||
params = [Puk1('01234567'), Puk2(98765432), Pin1('1111'), Pin2(2222), Adm1('11111111'),
|
||||
K(h2b('000102030405060708090a0b0c0d0e0f')), Opc(h2b('101112131415161718191a1b1c1d1e1f'))]
|
||||
for p in params:
|
||||
p.validate()
|
||||
p.apply(pes)
|
||||
# TODO: we don't actually test the results here, but we just verify there is no exception
|
||||
pes.to_der()
|
||||
|
||||
def test_constructor_encode(self):
|
||||
"""Test that DER-encoding of PE created by "empty" constructor works without raising exception."""
|
||||
for cls in [ProfileElementMF, ProfileElementPuk, ProfileElementPin, ProfileElementTelecom,
|
||||
ProfileElementUSIM, ProfileElementISIM, ProfileElementAKA, ProfileElementSD,
|
||||
ProfileElementSSD, ProfileElementOptUSIM, ProfileElementOptISIM,
|
||||
ProfileElementHeader, ProfileElementEnd]:
|
||||
with self.subTest(cls.__name__):
|
||||
pes = ProfileElementSequence()
|
||||
inst = cls()
|
||||
pes.append(inst)
|
||||
pes.to_der()
|
||||
|
||||
# RFM requires some constructor arguments
|
||||
cls = ProfileElementRFM
|
||||
with self.subTest(cls.__name__):
|
||||
pes = ProfileElementSequence()
|
||||
inst = cls(inst_aid=b'\x01\x02', sd_aid=b'\x03\x04', tar_list=[b'\x01\x02\x03'])
|
||||
pes.append(inst)
|
||||
pes.to_der()
|
||||
|
||||
class OidTest(unittest.TestCase):
|
||||
def test_cmp(self):
|
||||
self.assertTrue(oid.OID('1.0') > oid.OID('0.9'))
|
||||
self.assertTrue(oid.OID('1.0') == oid.OID('1.0'))
|
||||
self.assertTrue(oid.OID('1.0.1') > oid.OID('1.0'))
|
||||
self.assertTrue(oid.OID('1.0.2') > oid.OID('1.0.1'))
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
Executable
+31
@@ -0,0 +1,31 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import unittest
|
||||
|
||||
from pySim.euicc import *
|
||||
|
||||
class TestEid(unittest.TestCase):
|
||||
|
||||
def test_eid_verify(self):
|
||||
for eid in ['89049032123451234512345678901235', '89086030202200000022000023022943',
|
||||
'89044045116727494800000004479366', 89044045116727494800000004479366]:
|
||||
self.assertTrue(verify_eid_checksum(eid))
|
||||
|
||||
def test_eid_verify_wrong(self):
|
||||
self.assertFalse(verify_eid_checksum('89049032123451234512345678901234'))
|
||||
self.assertFalse(verify_eid_checksum(89049032123451234512345678901234))
|
||||
|
||||
def test_eid_encode_with_32_digits(self):
|
||||
self.assertEqual(compute_eid_checksum('89049032123451234512345678901200'), '89049032123451234512345678901235')
|
||||
self.assertEqual(compute_eid_checksum('89086030202200000022000023022900'), '89086030202200000022000023022943')
|
||||
|
||||
def test_eid_encode_with_30digits(self):
|
||||
self.assertEqual(compute_eid_checksum('890490321234512345123456789012'), '89049032123451234512345678901235')
|
||||
|
||||
def test_eid_encode_with_wrong_csum(self):
|
||||
# input: EID with wrong checksum
|
||||
self.assertEqual(compute_eid_checksum('89049032123451234512345678901299'), '89049032123451234512345678901235')
|
||||
self.assertEqual(compute_eid_checksum(89049032123451234512345678901299), '89049032123451234512345678901235')
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
Executable
+295
@@ -0,0 +1,295 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
# (C) 2023 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 pySim.utils import *
|
||||
from pySim.filesystem import *
|
||||
|
||||
import pySim.iso7816_4
|
||||
import pySim.ts_102_221
|
||||
import pySim.ts_102_222
|
||||
import pySim.ts_31_102
|
||||
import pySim.ts_31_103
|
||||
import pySim.ts_51_011
|
||||
import pySim.sysmocom_sja2
|
||||
import pySim.gsm_r
|
||||
import pySim.cdma_ruim
|
||||
|
||||
def get_qualified_name(c):
|
||||
"""return the qualified (by module) name of a class."""
|
||||
return "%s.%s" % (c.__module__, c.__name__)
|
||||
|
||||
class LinFixed_Test(unittest.TestCase):
|
||||
classes = all_subclasses(LinFixedEF)
|
||||
maxDiff = None
|
||||
|
||||
@staticmethod
|
||||
def _parse_t(t):
|
||||
"""Parse a test description which can either be a 2-tuple of (enc, dec) or
|
||||
a 3-tuple of (enc, rec_nr, dec)."""
|
||||
if len(t) == 2:
|
||||
encoded = t[0]
|
||||
rec_num = 1
|
||||
decoded = t[1]
|
||||
else:
|
||||
encoded = t[0]
|
||||
rec_num = t[1]
|
||||
decoded = t[2]
|
||||
return encoded, rec_num, decoded
|
||||
|
||||
def test_decode_record(self):
|
||||
"""Test the decoder for a linear-fixed EF. Requires the given LinFixedEF subclass
|
||||
to have an '_test_decode' attribute, containing a list of tuples. Each tuple can
|
||||
either be a
|
||||
* 2-tuple (hexstring, decoded_dict) or a
|
||||
* 3-tuple (hexstring, record_nr, decoded_dict)
|
||||
"""
|
||||
for c in self.classes:
|
||||
name = get_qualified_name(c)
|
||||
if hasattr(c, '_test_decode'):
|
||||
for t in c._test_decode:
|
||||
encoded, rec_num, decoded = self._parse_t(t)
|
||||
with self.subTest(name, test_decode=t):
|
||||
inst = c()
|
||||
logging.debug("Testing decode of %s", name)
|
||||
re_dec = inst.decode_record_hex(encoded, rec_num)
|
||||
self.assertEqual(decoded, re_dec)
|
||||
if hasattr(c, '_test_no_pad') and c._test_no_pad:
|
||||
continue
|
||||
with self.subTest(name, test_decode_padded=t):
|
||||
encoded = encoded + 'ff'
|
||||
inst = c()
|
||||
logging.debug("Testing padded decode of %s", name)
|
||||
re_dec = inst.decode_record_hex(encoded, rec_num)
|
||||
self.assertEqual(decoded, re_dec)
|
||||
|
||||
def test_encode_record(self):
|
||||
"""Test the encoder for a linear-fixed EF. Requires the given LinFixedEF subclass
|
||||
to have an '_test_encode' attribute, containing a list of tuples. Each tuple can
|
||||
either be a
|
||||
* 2-tuple (hexstring, decoded_dict) or a
|
||||
* 3-tuple (hexstring, record_nr, decoded_dict)
|
||||
"""
|
||||
for c in self.classes:
|
||||
name = get_qualified_name(c)
|
||||
if hasattr(c, '_test_encode'):
|
||||
for t in c._test_encode:
|
||||
with self.subTest(name, test_encode=t):
|
||||
inst = c()
|
||||
encoded, rec_num, decoded = self._parse_t(t)
|
||||
logging.debug("Testing encode of %s", name)
|
||||
re_enc = inst.encode_record_hex(decoded, rec_num)
|
||||
self.assertEqual(encoded.upper(), re_enc.upper())
|
||||
|
||||
def test_de_encode_record(self):
|
||||
"""Test the decoder and encoder for a linear-fixed EF. Performs first a decoder
|
||||
test, and then re-encodes the decoded data, comparing the re-encoded data with the
|
||||
initial input data.
|
||||
|
||||
Requires the given LinFixedEF subclass to have a '_test_de_encode' attribute,
|
||||
containing a list of tuples. Each tuple can
|
||||
either be a
|
||||
* 2-tuple (hexstring, decoded_dict) or a
|
||||
* 3-tuple (hexstring, record_nr, decoded_dict)
|
||||
"""
|
||||
for c in self.classes:
|
||||
name = get_qualified_name(c)
|
||||
if hasattr(c, '_test_de_encode'):
|
||||
for t in c._test_de_encode:
|
||||
encoded, rec_num, decoded = self._parse_t(t)
|
||||
with self.subTest(name, test_de_encode=t):
|
||||
inst = c()
|
||||
logging.debug("Testing decode of %s", name)
|
||||
re_dec = inst.decode_record_hex(encoded, rec_num)
|
||||
self.assertEqual(decoded, re_dec)
|
||||
# re-encode the decoded data
|
||||
logging.debug("Testing re-encode of %s", name)
|
||||
re_enc = inst.encode_record_hex(re_dec, rec_num)
|
||||
self.assertEqual(encoded.upper(), re_enc.upper())
|
||||
if hasattr(c, '_test_no_pad') and c._test_no_pad:
|
||||
continue
|
||||
with self.subTest(name, test_decode_padded=t):
|
||||
encoded = encoded + 'ff'
|
||||
inst = c()
|
||||
logging.debug("Testing padded decode of %s", name)
|
||||
re_dec = inst.decode_record_hex(encoded, rec_num)
|
||||
self.assertEqual(decoded, re_dec)
|
||||
|
||||
|
||||
class TransRecEF_Test(unittest.TestCase):
|
||||
classes = all_subclasses(TransRecEF)
|
||||
maxDiff = None
|
||||
|
||||
def test_decode_record(self):
|
||||
"""Test the decoder for a transparent record-oriented EF. Requires the given TransRecEF subclass
|
||||
to have an '_test_decode' attribute, containing a list of tuples. Each tuple has to be a
|
||||
2-tuple (hexstring, decoded_dict).
|
||||
"""
|
||||
for c in self.classes:
|
||||
name = get_qualified_name(c)
|
||||
if hasattr(c, '_test_decode'):
|
||||
for t in c._test_decode:
|
||||
with self.subTest(name, test_decode=t):
|
||||
inst = c()
|
||||
encoded = t[0]
|
||||
decoded = t[1]
|
||||
logging.debug("Testing decode of %s", name)
|
||||
re_dec = inst.decode_record_hex(encoded)
|
||||
self.assertEqual(decoded, re_dec)
|
||||
# there's no point in testing padded input, as TransRecEF have a fixed record
|
||||
# size and we cannot ever receive more input data than that size.
|
||||
|
||||
def test_encode_record(self):
|
||||
"""Test the encoder for a transparent record-oriented EF. Requires the given TransRecEF subclass
|
||||
to have an '_test_encode' attribute, containing a list of tuples. Each tuple has to be a
|
||||
2-tuple (hexstring, decoded_dict).
|
||||
"""
|
||||
for c in self.classes:
|
||||
name = get_qualified_name(c)
|
||||
if hasattr(c, '_test_decode'):
|
||||
for t in c._test_decode:
|
||||
with self.subTest(name, test_decode=t):
|
||||
inst = c()
|
||||
encoded = t[0]
|
||||
decoded = t[1]
|
||||
logging.debug("Testing decode of %s", name)
|
||||
re_dec = inst.decode_record_hex(encoded)
|
||||
self.assertEqual(decoded, re_dec)
|
||||
|
||||
|
||||
def test_de_encode_record(self):
|
||||
"""Test the decoder and encoder for a transparent record-oriented EF. Performs first a decoder
|
||||
test, and then re-encodes the decoded data, comparing the re-encoded data with the
|
||||
initial input data.
|
||||
|
||||
Requires the given TransRecEF subclass to have a '_test_de_encode' attribute,
|
||||
containing a list of tuples. Each tuple has to be a 2-tuple (hexstring, decoded_dict).
|
||||
"""
|
||||
for c in self.classes:
|
||||
name = get_qualified_name(c)
|
||||
if hasattr(c, '_test_de_encode'):
|
||||
for t in c._test_de_encode:
|
||||
with self.subTest(name, test_de_encode=t):
|
||||
inst = c()
|
||||
encoded = t[0]
|
||||
decoded = t[1]
|
||||
logging.debug("Testing decode of %s", name)
|
||||
re_dec = inst.decode_record_hex(encoded)
|
||||
self.assertEqual(decoded, re_dec)
|
||||
# re-encode the decoded data
|
||||
logging.debug("Testing re-encode of %s", name)
|
||||
re_enc = inst.encode_record_hex(re_dec)
|
||||
self.assertEqual(encoded.upper(), re_enc.upper())
|
||||
# there's no point in testing padded input, as TransRecEF have a fixed record
|
||||
# size and we cannot ever receive more input data than that size.
|
||||
|
||||
|
||||
class TransparentEF_Test(unittest.TestCase):
|
||||
maxDiff = None
|
||||
|
||||
@classmethod
|
||||
def get_classes(cls):
|
||||
"""get list of TransparentEF sub-classes which are not a TransRecEF subclass."""
|
||||
classes = all_subclasses(TransparentEF)
|
||||
trans_rec_classes = all_subclasses(TransRecEF)
|
||||
return filter(lambda c: c not in trans_rec_classes, classes)
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
"""set-up method called once for this class by unittest framework"""
|
||||
cls.classes = cls.get_classes()
|
||||
|
||||
def test_decode_file(self):
|
||||
"""Test the decoder for a transparent EF. Requires the given TransparentEF subclass
|
||||
to have a '_test_decode' attribute, containing a list of tuples. Each tuple
|
||||
is a 2-tuple (hexstring, decoded_dict).
|
||||
"""
|
||||
for c in self.classes:
|
||||
name = get_qualified_name(c)
|
||||
if hasattr(c, '_test_decode'):
|
||||
for t in c._test_decode:
|
||||
encoded = t[0]
|
||||
decoded = t[1]
|
||||
with self.subTest(name, test_decode=t):
|
||||
inst = c()
|
||||
logging.debug("Testing decode of %s", name)
|
||||
re_dec = inst.decode_hex(encoded)
|
||||
self.assertEqual(decoded, re_dec)
|
||||
if hasattr(c, '_test_no_pad') and c._test_no_pad:
|
||||
continue
|
||||
with self.subTest(name, test_decode_padded=t):
|
||||
encoded = encoded + 'ff'
|
||||
inst = c()
|
||||
logging.debug("Testing padded decode of %s", name)
|
||||
re_dec = inst.decode_hex(encoded)
|
||||
self.assertEqual(decoded, re_dec)
|
||||
|
||||
def test_encode_file(self):
|
||||
"""Test the encoder for a transparent EF. Requires the given TransparentEF subclass
|
||||
to have a '_test_encode' attribute, containing a list of tuples. Each tuple
|
||||
is a 2-tuple (hexstring, decoded_dict).
|
||||
"""
|
||||
for c in self.classes:
|
||||
name = get_qualified_name(c)
|
||||
if hasattr(c, '_test_encode'):
|
||||
for t in c._test_encode:
|
||||
with self.subTest(name, test_encode=t):
|
||||
inst = c()
|
||||
encoded = t[0]
|
||||
decoded = t[1]
|
||||
logging.debug("Testing encode of %s", name)
|
||||
re_dec = inst.decode_hex(encoded)
|
||||
self.assertEqual(decoded, re_dec)
|
||||
|
||||
def test_de_encode_file(self):
|
||||
"""Test the decoder and encoder for a transparent EF. Performs first a decoder
|
||||
test, and then re-encodes the decoded data, comparing the re-encoded data with the
|
||||
initial input data.
|
||||
|
||||
Requires the given TransparentEF subclass to have a '_test_de_encode' attribute,
|
||||
containing a list of tuples. Each tuple is a 2-tuple (hexstring, decoded_dict).
|
||||
"""
|
||||
for c in self.classes:
|
||||
name = get_qualified_name(c)
|
||||
if hasattr(c, '_test_de_encode'):
|
||||
for t in c._test_de_encode:
|
||||
encoded = t[0]
|
||||
decoded = t[1]
|
||||
with self.subTest(name, test_de_encode=t):
|
||||
inst = c()
|
||||
logging.debug("Testing decode of %s", name)
|
||||
re_dec = inst.decode_hex(encoded)
|
||||
self.assertEqual(decoded, re_dec)
|
||||
logging.debug("Testing re-encode of %s", name)
|
||||
re_dec = inst.decode_hex(encoded)
|
||||
re_enc = inst.encode_hex(re_dec)
|
||||
self.assertEqual(encoded.upper(), re_enc.upper())
|
||||
if hasattr(c, '_test_no_pad') and c._test_no_pad:
|
||||
continue
|
||||
with self.subTest(name, test_decode_padded=t):
|
||||
encoded = encoded + 'ff'
|
||||
inst = c()
|
||||
logging.debug("Testing padded decode of %s", name)
|
||||
re_dec = inst.decode_hex(encoded)
|
||||
self.assertEqual(decoded, re_dec)
|
||||
|
||||
if __name__ == '__main__':
|
||||
logger = logging.getLogger()
|
||||
logger.setLevel(logging.DEBUG)
|
||||
unittest.main()
|
||||
@@ -0,0 +1,225 @@
|
||||
#!/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 pySim.global_platform import *
|
||||
from pySim.global_platform.scp import *
|
||||
from pySim.utils import b2h, h2b
|
||||
|
||||
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(), '805020000840A62C37FA6304F8')
|
||||
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')
|
||||
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):
|
||||
wrapped = self.scp02.wrap_cmd_apdu(h2b('80f28002024f00'))
|
||||
self.assertEqual(b2h(wrapped).upper(), '84F280020A4F00B21AAFA3EB2D1672')
|
||||
|
||||
|
||||
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_rsp_plain = h2b('bf3e125a1089882119900000000000000000000005')
|
||||
|
||||
# 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:]
|
||||
|
||||
@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))
|
||||
|
||||
|
||||
# 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('8050300008b13e5f938fc108c4')
|
||||
init_upd_rsp = h2b('000000000000000000003003703eb51047495b249f66c484c1d2ef1948000002')
|
||||
ext_auth_cmd = h2b('84821100107d5f5826a993ebc89eea24957fa0b3ce')
|
||||
get_eid_cmd = h2b('84e291000ebf3e035c015a558d036518a28297')
|
||||
get_eid_rsp = h2b('bf3e125a1089882119900000000000000000000005971be68992dbbdfa')
|
||||
|
||||
class SCP03_Test_AES128_03(SCP03_Test, unittest.TestCase):
|
||||
keyset = KEYSET_AES128
|
||||
init_upd_cmd = h2b('80503000088e1552d0513c60f3')
|
||||
init_upd_rsp = h2b('0000000000000000000030037030760cd2c47c1dd395065fe5ead8a9d7000001')
|
||||
ext_auth_cmd = h2b('8482030010fd4721a14d9b07003c451d2f8ae6bb21')
|
||||
get_eid_cmd = h2b('84e2910018ca9c00f6713d79bc8baa642bdff51c3f6a4082d3bd9ad26c')
|
||||
get_eid_rsp = h2b('bf3e125a1089882119900000000000000000000005')
|
||||
|
||||
class SCP03_Test_AES128_33(SCP03_Test, unittest.TestCase):
|
||||
keyset = KEYSET_AES128
|
||||
init_upd_cmd = h2b('8050300008fdf38259a1e0de44')
|
||||
init_upd_rsp = h2b('000000000000000000003003703b1aca81e821f219081cdc01c26b372d000003')
|
||||
ext_auth_cmd = h2b('84823300108c36f96bcc00724a4e13ad591d7da3f0')
|
||||
get_eid_cmd = h2b('84e2910018267a85dfe4a98fca6fb0527e0dfecce4914e40401433c87f')
|
||||
get_eid_rsp = h2b('f3ba2b1013aa6224f5e1c138d71805c569e5439b47576260b75fc021b25097cb2e68f8a0144975b9')
|
||||
|
||||
class SCP03_Test_AES192_11(SCP03_Test, unittest.TestCase):
|
||||
keyset = KEYSET_AES192
|
||||
init_upd_cmd = h2b('80503100087396430b768b085b')
|
||||
init_upd_rsp = h2b('000000000000000000003103708cfc23522ffdbf1e5df5542cac8fd866000003')
|
||||
ext_auth_cmd = h2b('84821100102145ed30b146f5db252fb7e624cec244')
|
||||
get_eid_cmd = h2b('84e291000ebf3e035c015aff42cf801d143944')
|
||||
get_eid_rsp = h2b('bf3e125a1089882119900000000000000000000005162fbd33e04940a9')
|
||||
|
||||
class SCP03_Test_AES192_03(SCP03_Test, unittest.TestCase):
|
||||
keyset = KEYSET_AES192
|
||||
init_upd_cmd = h2b('805031000869c65da8202bf19f')
|
||||
init_upd_rsp = h2b('00000000000000000000310370b570a67be38446717729d6dd3d2ec5b1000001')
|
||||
ext_auth_cmd = h2b('848203001065df4f1a356a887905466516d9e5b7c1')
|
||||
get_eid_cmd = h2b('84e2910018d2c6fb477c5d4afe4fd4d21f17eff10d3578ec1774a12a2d')
|
||||
get_eid_rsp = h2b('bf3e125a1089882119900000000000000000000005')
|
||||
|
||||
class SCP03_Test_AES192_33(SCP03_Test, unittest.TestCase):
|
||||
keyset = KEYSET_AES192
|
||||
init_upd_cmd = h2b('80503100089b3f2eef0e8c9374')
|
||||
init_upd_rsp = h2b('00000000000000000000310370f6bb305a15bae1a68f79fb08212fbed7000002')
|
||||
ext_auth_cmd = h2b('84823300109100bc22d58b45b86a26365ce39ff3cf')
|
||||
get_eid_cmd = h2b('84e29100188f7f946c84f70d17994bc6e8791251bb1bb1bf02cf8de589')
|
||||
get_eid_rsp = h2b('c05176c1b6f72aae50c32cbee63b0e95998928fd4dfb2be9f27ffde8c8476f5909b4805cc4039599')
|
||||
|
||||
class SCP03_Test_AES256_11(SCP03_Test, unittest.TestCase):
|
||||
keyset = KEYSET_AES256
|
||||
init_upd_cmd = h2b('805032000811666d57866c6f54')
|
||||
init_upd_rsp = h2b('0000000000000000000032037053ea8847efa7674e41498a4d66cf0dee000003')
|
||||
ext_auth_cmd = h2b('84821100102f2ad190eff2fafc4908996d1cebd310')
|
||||
get_eid_cmd = h2b('84e291000ebf3e035c015af4b680372542b59d')
|
||||
get_eid_rsp = h2b('bf3e125a10898821199000000000000000000000058012dd7f01f1c4c1')
|
||||
|
||||
class SCP03_Test_AES256_03(SCP03_Test, unittest.TestCase):
|
||||
keyset = KEYSET_AES256
|
||||
init_upd_cmd = h2b('8050320008c6066990fc426e1d')
|
||||
init_upd_rsp = h2b('000000000000000000003203708682cd81bbd8919f2de3f2664581f118000001')
|
||||
ext_auth_cmd = h2b('848203001077c493b632edadaf865a1e64acc07ce9')
|
||||
get_eid_cmd = h2b('84e29100183ddaa60594963befaada3525b492ede23c2ab2c1ce3afe44')
|
||||
get_eid_rsp = h2b('bf3e125a1089882119900000000000000000000005')
|
||||
|
||||
class SCP03_Test_AES256_33(SCP03_Test, unittest.TestCase):
|
||||
keyset = KEYSET_AES256
|
||||
init_upd_cmd = h2b('805032000897b2055fe58599fd')
|
||||
init_upd_rsp = h2b('00000000000000000000320370a8439a22cedf045fa9f1903b2834f26e000002')
|
||||
ext_auth_cmd = h2b('8482330010508a0fd959d2e547c6b33154a6be2057')
|
||||
get_eid_cmd = h2b('84e29100187a5ef717eaf1e135ae92fe54429d0e465decda65f5fe5aea')
|
||||
get_eid_rsp = h2b('ea90dbfa648a67c5eb6abc57f8530b97d0cd5647c5e8732016b55203b078dd2ace7f8bc5d1c1cd99')
|
||||
|
||||
# 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()
|
||||
@@ -0,0 +1,304 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import unittest
|
||||
from pySim.utils import h2b, b2h
|
||||
from pySim.sms import SMS_SUBMIT, SMS_DELIVER, AddressField
|
||||
from pySim.ota import *
|
||||
|
||||
# pre-defined SPI values for use in test cases below
|
||||
SPI_CC_POR_CIPHERED_CC = {
|
||||
'counter':'no_counter',
|
||||
'ciphering':True,
|
||||
'rc_cc_ds': 'cc',
|
||||
'por_in_submit':False,
|
||||
'por_shall_be_ciphered':True,
|
||||
'por_rc_cc_ds': 'cc',
|
||||
'por': 'por_required'
|
||||
}
|
||||
|
||||
SPI_CC_POR_UNCIPHERED_CC = {
|
||||
'counter':'no_counter',
|
||||
'ciphering':True,
|
||||
'rc_cc_ds': 'cc',
|
||||
'por_in_submit':False,
|
||||
'por_shall_be_ciphered':False,
|
||||
'por_rc_cc_ds': 'cc',
|
||||
'por': 'por_required'
|
||||
}
|
||||
|
||||
SPI_CC_POR_UNCIPHERED_NOCC = {
|
||||
'counter':'no_counter',
|
||||
'ciphering':True,
|
||||
'rc_cc_ds': 'cc',
|
||||
'por_in_submit':False,
|
||||
'por_shall_be_ciphered':False,
|
||||
'por_rc_cc_ds': 'no_rc_cc_ds',
|
||||
'por': 'por_required'
|
||||
}
|
||||
|
||||
######################################################################
|
||||
# old-style code-driven test (lots of code copy+paste)
|
||||
######################################################################
|
||||
|
||||
class Test_SMS_AES128(unittest.TestCase):
|
||||
tar = h2b('B00011')
|
||||
"""Test the OtaDialectSms for AES128 algorithms."""
|
||||
def __init__(self, foo, **kwargs):
|
||||
super().__init__(foo, **kwargs)
|
||||
self.od = OtaKeyset(algo_crypt='aes_cbc', kic_idx=2,
|
||||
algo_auth='aes_cmac', kid_idx=2,
|
||||
kic=h2b('200102030405060708090a0b0c0d0e0f'),
|
||||
kid=h2b('201102030405060708090a0b0c0d0e0f'))
|
||||
self.dialect = OtaDialectSms()
|
||||
self.spi_base = SPI_CC_POR_CIPHERED_CC
|
||||
|
||||
def _check_response(self, r, d):
|
||||
self.assertEqual(d['number_of_commands'], 1)
|
||||
self.assertEqual(d['last_status_word'], '6132')
|
||||
self.assertEqual(d['last_response_data'], u'')
|
||||
self.assertEqual(r['response_status'], 'por_ok')
|
||||
|
||||
def test_resp_aes128_ciphered(self):
|
||||
spi = self.spi_base
|
||||
r, d = self.dialect.decode_resp(self.od, spi, '027100002412b00011ebc6b497e2cad7aedf36ace0e3a29b38853f0fe9ccde81913be5702b73abce1f')
|
||||
self._check_response(r, d)
|
||||
|
||||
def test_cmd_aes128_ciphered(self):
|
||||
spi = self.spi_base
|
||||
apdu = h2b('00a40004023f00')
|
||||
r = self.dialect.encode_cmd(self.od, self.tar, spi, apdu)
|
||||
self.assertEqual(b2h(r), '00281506192222b00011e87cceebb2d93083011ce294f93fc4d8de80da1abae8c37ca3e72ec4432e5058')
|
||||
# also test decoder
|
||||
dec_tar, dec_spi, dec_apdu = self.dialect.decode_cmd(self.od, r)
|
||||
self.assertEqual(b2h(apdu), b2h(dec_apdu))
|
||||
self.assertEqual(b2h(dec_tar), b2h(self.tar))
|
||||
self.assertEqual(dec_spi, spi)
|
||||
|
||||
|
||||
class Test_SMS_3DES(unittest.TestCase):
|
||||
tar = h2b('b00000')
|
||||
apdu = h2b('00a40000023f00')
|
||||
"""Test the OtaDialectSms for 3DES algorithms."""
|
||||
def __init__(self, foo, **kwargs):
|
||||
super().__init__(foo, **kwargs)
|
||||
# KIC1 + KID1 of 8988211000000467285
|
||||
KIC1 = h2b('D0FDA31990D8D64178601317191669B4')
|
||||
KID1 = h2b('D24EB461799C5E035C77451FD9404463')
|
||||
KIC3 = h2b('C21DD66ACAC13CB3BC8B331B24AFB57B')
|
||||
KID3 = h2b('12110C78E678C25408233076AA033615')
|
||||
self.od = OtaKeyset(algo_crypt='triple_des_cbc2', kic_idx=3, kic=KIC3,
|
||||
algo_auth='triple_des_cbc2', kid_idx=3, kid=KID3)
|
||||
self.dialect = OtaDialectSms()
|
||||
self.spi_base = {
|
||||
'counter':'no_counter',
|
||||
'ciphering': True,
|
||||
'rc_cc_ds': 'cc',
|
||||
'por_in_submit':False,
|
||||
'por': 'por_required',
|
||||
'por_shall_be_ciphered': True,
|
||||
'por_rc_cc_ds': 'cc',
|
||||
}
|
||||
|
||||
def _check_response(self, r, d):
|
||||
self.assertEqual(d['number_of_commands'], 1)
|
||||
self.assertEqual(d['last_status_word'], '612f')
|
||||
self.assertEqual(d['last_response_data'], u'')
|
||||
self.assertEqual(r['response_status'], 'por_ok')
|
||||
|
||||
def test_resp_3des_ciphered(self):
|
||||
spi = self.spi_base
|
||||
spi['por_shall_be_ciphered'] = True
|
||||
spi['por_rc_cc_ds'] = 'cc'
|
||||
r, d = self.dialect.decode_resp(self.od, spi, '027100001c12b000119660ebdb81be189b5e4389e9e7ab2bc0954f963ad869ed7c')
|
||||
self._check_response(r, d)
|
||||
|
||||
def test_resp_3des_signed(self):
|
||||
spi = self.spi_base
|
||||
spi['por_shall_be_ciphered'] = False
|
||||
spi['por_rc_cc_ds'] = 'cc'
|
||||
r, d = self.dialect.decode_resp(self.od, spi, '027100001612b000110000000000000055f47118381175fb01612f')
|
||||
self._check_response(r, d)
|
||||
|
||||
def test_resp_3des_signed_err(self):
|
||||
"""Expect an OtaCheckError exception if the computed CC != received CC"""
|
||||
spi = self.spi_base
|
||||
spi['por_shall_be_ciphered'] = False
|
||||
spi['por_rc_cc_ds'] = 'cc'
|
||||
with self.assertRaises(OtaCheckError) as context:
|
||||
r, d = self.dialect.decode_resp(self.od, spi, '027100001612b000110000000000000055f47118381175fb02612f')
|
||||
self.assertTrue('!= Computed CC' in str(context.exception))
|
||||
|
||||
def test_resp_3des_none(self):
|
||||
spi = self.spi_base
|
||||
spi['por_shall_be_ciphered'] = False
|
||||
spi['por_rc_cc_ds'] = 'no_rc_cc_ds'
|
||||
r, d = self.dialect.decode_resp(self.od, spi, '027100000e0ab000110000000000000001612f')
|
||||
self._check_response(r, d)
|
||||
|
||||
def test_cmd_3des_ciphered(self):
|
||||
spi = self.spi_base
|
||||
spi['ciphering'] = True
|
||||
spi['rc_cc_ds'] = 'no_rc_cc_ds'
|
||||
r = self.dialect.encode_cmd(self.od, self.tar, spi, self.apdu)
|
||||
self.assertEqual(b2h(r), '00180d04193535b00000e3ec80a849b554421276af3883927c20')
|
||||
# also test decoder
|
||||
dec_tar, dec_spi, dec_apdu = self.dialect.decode_cmd(self.od, r)
|
||||
self.assertEqual(b2h(self.apdu), b2h(dec_apdu))
|
||||
self.assertEqual(b2h(dec_tar), b2h(self.tar))
|
||||
self.assertEqual(dec_spi, spi)
|
||||
|
||||
def test_cmd_3des_signed(self):
|
||||
spi = self.spi_base
|
||||
spi['ciphering'] = False
|
||||
spi['rc_cc_ds'] = 'cc'
|
||||
r = self.dialect.encode_cmd(self.od, self.tar, spi, self.apdu)
|
||||
self.assertEqual(b2h(r), '1502193535b00000000000000000072ea17bdb72060e00a40000023f00')
|
||||
|
||||
def test_cmd_3des_none(self):
|
||||
spi = self.spi_base
|
||||
spi['ciphering'] = False
|
||||
spi['rc_cc_ds'] = 'no_rc_cc_ds'
|
||||
r = self.dialect.encode_cmd(self.od, self.tar, spi, self.apdu)
|
||||
self.assertEqual(b2h(r), '0d00193535b0000000000000000000a40000023f00')
|
||||
|
||||
|
||||
|
||||
######################################################################
|
||||
# new-style data-driven tests
|
||||
######################################################################
|
||||
|
||||
# SJA5 SAMPLE cards provisioned by execute_ipr.py
|
||||
OTA_KEYSET_SJA5_SAMPLES = OtaKeyset(algo_crypt='triple_des_cbc2', kic_idx=3,
|
||||
algo_auth='triple_des_cbc2', kid_idx=3,
|
||||
kic=h2b('300102030405060708090a0b0c0d0e0f'),
|
||||
kid=h2b('301102030405060708090a0b0c0d0e0f'))
|
||||
|
||||
OTA_KEYSET_SJA5_AES128 = OtaKeyset(algo_crypt='aes_cbc', kic_idx=2,
|
||||
algo_auth='aes_cmac', kid_idx=2,
|
||||
kic=h2b('200102030405060708090a0b0c0d0e0f'),
|
||||
kid=h2b('201102030405060708090a0b0c0d0e0f'))
|
||||
|
||||
class OtaTestCase(unittest.TestCase):
|
||||
def __init__(self, methodName='runTest', **kwargs):
|
||||
super().__init__(methodName, **kwargs)
|
||||
# RAM: B00000
|
||||
# SIM RFM: B00010
|
||||
# USIM RFM: B00011
|
||||
self.tar = h2b('B00011')
|
||||
|
||||
class SmsOtaTestCase(OtaTestCase):
|
||||
# Array describing the input/output data for the tests. We use the
|
||||
# unittest subTests context manager to iterate over the entries of
|
||||
# this testdatasets list. This is much more productive than
|
||||
# manually writing one class per test.
|
||||
testdatasets = [
|
||||
{
|
||||
'name': '3DES-SJA5-CIPHERED-CC',
|
||||
'ota_keyset': OTA_KEYSET_SJA5_SAMPLES,
|
||||
'spi': SPI_CC_POR_CIPHERED_CC,
|
||||
'request': {
|
||||
'apdu': b'\x00\xa4\x00\x04\x02\x3f\x00',
|
||||
'encoded_cmd': '00201506193535b00011ae733256918d050b87c94fbfe12e4dc402f262c41cf67f2f',
|
||||
'encoded_tpdu': '400881214365877ff6227052000000000302700000201506193535b00011ae733256918d050b87c94fbfe12e4dc402f262c41cf67f2f',
|
||||
},
|
||||
'response': {
|
||||
'encoded_resp': '027100001c12b000118bb989492c632529326a2f4681feb37c825bc9021c9f6d0b',
|
||||
'response_status': 'por_ok',
|
||||
'number_of_commands': 1,
|
||||
'last_status_word': '6132',
|
||||
'last_response_data': '',
|
||||
}
|
||||
}, {
|
||||
'name': '3DES-SJA5-UNCIPHERED-CC',
|
||||
'ota_keyset': OTA_KEYSET_SJA5_SAMPLES,
|
||||
'spi': SPI_CC_POR_UNCIPHERED_CC,
|
||||
'request': {
|
||||
'apdu': b'\x00\xa4\x00\x04\x02\x3f\x00',
|
||||
'encoded_cmd': '00201506093535b00011c49ac91ab8159ba5b83a54fb6385e0a5e31694f8b215fafc',
|
||||
'encoded_tpdu': '400881214365877ff6227052000000000302700000201506093535b00011c49ac91ab8159ba5b83a54fb6385e0a5e31694f8b215fafc',
|
||||
},
|
||||
'response': {
|
||||
'encoded_resp': '027100001612b0001100000000000000b5bcd6353a421fae016132',
|
||||
'response_status': 'por_ok',
|
||||
'number_of_commands': 1,
|
||||
'last_status_word': '6132',
|
||||
'last_response_data': '',
|
||||
}
|
||||
}, {
|
||||
'name': '3DES-SJA5-UNCIPHERED-NOCC',
|
||||
'ota_keyset': OTA_KEYSET_SJA5_SAMPLES,
|
||||
'spi': SPI_CC_POR_UNCIPHERED_NOCC,
|
||||
'request': {
|
||||
'apdu': b'\x00\xa4\x00\x04\x02\x3f\x00',
|
||||
'encoded_cmd': '00201506013535b000113190be334900f52b025f3f7eddfe868e96ebf310023b7769',
|
||||
'encoded_tpdu': '400881214365877ff6227052000000000302700000201506013535b000113190be334900f52b025f3f7eddfe868e96ebf310023b7769',
|
||||
},
|
||||
'response': {
|
||||
'encoded_resp': '027100000e0ab0001100000000000000016132',
|
||||
'response_status': 'por_ok',
|
||||
'number_of_commands': 1,
|
||||
'last_status_word': '6132',
|
||||
'last_response_data': '',
|
||||
}
|
||||
}, {
|
||||
'name': 'AES128-SJA5-CIPHERED-CC',
|
||||
'ota_keyset': OTA_KEYSET_SJA5_AES128,
|
||||
'spi': SPI_CC_POR_CIPHERED_CC,
|
||||
'request': {
|
||||
'apdu': b'\x00\xa4\x00\x04\x02\x3f\x00',
|
||||
'encoded_cmd': '00281506192222b00011e87cceebb2d93083011ce294f93fc4d8de80da1abae8c37ca3e72ec4432e5058',
|
||||
'encoded_tpdu': '400881214365877ff6227052000000000302700000281506192222b00011e87cceebb2d93083011ce294f93fc4d8de80da1abae8c37ca3e72ec4432e5058',
|
||||
},
|
||||
'response': {
|
||||
'encoded_resp': '027100002412b00011ebc6b497e2cad7aedf36ace0e3a29b38853f0fe9ccde81913be5702b73abce1f',
|
||||
'response_status': 'por_ok',
|
||||
'number_of_commands': 1,
|
||||
'last_status_word': '6132',
|
||||
'last_response_data': '',
|
||||
}
|
||||
},
|
||||
# TODO: AES192
|
||||
# TODO: AES256
|
||||
]
|
||||
|
||||
def __init__(self, methodName='runTest', **kwargs):
|
||||
super().__init__(methodName, **kwargs)
|
||||
self.dialect = OtaDialectSms()
|
||||
self.da = AddressField('12345678', 'unknown', 'isdn_e164')
|
||||
|
||||
def test_encode_cmd(self):
|
||||
for t in SmsOtaTestCase.testdatasets:
|
||||
with self.subTest(name=t['name']):
|
||||
kset = t['ota_keyset']
|
||||
outp = self.dialect.encode_cmd(kset, self.tar, t['spi'], apdu=t['request']['apdu'])
|
||||
#print("result: %s" % b2h(outp))
|
||||
self.assertEqual(b2h(outp), t['request']['encoded_cmd'])
|
||||
|
||||
with_udh = b'\x02\x70\x00' + outp
|
||||
#print("with_udh: %s" % b2h(with_udh))
|
||||
|
||||
tpdu = SMS_DELIVER(tp_udhi=True, tp_oa=self.da, tp_pid=0x7F, tp_dcs=0xF6,
|
||||
tp_scts=h2b('22705200000000'), tp_udl=3, tp_ud=with_udh)
|
||||
#print("TPDU: %s" % tpdu)
|
||||
#print("tpdu: %s" % b2h(tpdu.to_bytes()))
|
||||
self.assertEqual(b2h(tpdu.to_bytes()), t['request']['encoded_tpdu'])
|
||||
|
||||
# also test decoder
|
||||
dec_tar, dec_spi, dec_apdu = self.dialect.decode_cmd(kset, outp)
|
||||
self.assertEqual(b2h(t['request']['apdu']), b2h(dec_apdu))
|
||||
self.assertEqual(b2h(dec_tar), b2h(self.tar))
|
||||
self.assertEqual(dec_spi, t['spi'])
|
||||
|
||||
def test_decode_resp(self):
|
||||
for t in SmsOtaTestCase.testdatasets:
|
||||
with self.subTest(name=t['name']):
|
||||
kset = t['ota_keyset']
|
||||
r, d = self.dialect.decode_resp(kset, t['spi'], t['response']['encoded_resp'])
|
||||
#print("RESP: %s / %s" % (r, d))
|
||||
self.assertEqual(r.response_status, t['response']['response_status'])
|
||||
self.assertEqual(d.number_of_commands, t['response']['number_of_commands'])
|
||||
self.assertEqual(d.last_status_word, t['response']['last_status_word'])
|
||||
self.assertEqual(d.last_response_data, t['response']['last_response_data'])
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
@@ -0,0 +1,105 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import unittest
|
||||
from pySim.utils import h2b, b2h
|
||||
from pySim.sms import *
|
||||
|
||||
class Test_SMS_UDH(unittest.TestCase):
|
||||
def test_single_ie(self):
|
||||
udh, tail = UserDataHeader.from_bytes('027100')
|
||||
self.assertEqual(len(udh.ies), 1)
|
||||
ie = udh.ies[0]
|
||||
self.assertEqual(ie.iei, 0x71)
|
||||
self.assertEqual(ie.length, 0)
|
||||
self.assertEqual(ie.value, b'')
|
||||
self.assertEqual(tail, b'')
|
||||
|
||||
def test_single_ie_tail(self):
|
||||
udh, tail = UserDataHeader.from_bytes('027100abcdef')
|
||||
self.assertEqual(len(udh.ies), 1)
|
||||
ie = udh.ies[0]
|
||||
self.assertEqual(ie.iei, 0x71)
|
||||
self.assertEqual(ie.length, 0)
|
||||
self.assertEqual(ie.value, b'')
|
||||
self.assertEqual(tail, b'\xab\xcd\xef')
|
||||
|
||||
def test_single_ie_value(self):
|
||||
udh, tail = UserDataHeader.from_bytes('03710110')
|
||||
self.assertEqual(len(udh.ies), 1)
|
||||
ie = udh.ies[0]
|
||||
self.assertEqual(ie.iei, 0x71)
|
||||
self.assertEqual(ie.length, 1)
|
||||
self.assertEqual(ie.value, b'\x10')
|
||||
self.assertEqual(tail, b'')
|
||||
|
||||
def test_two_ie_data_tail(self):
|
||||
udh, tail = UserDataHeader.from_bytes('0571007001ffabcd')
|
||||
self.assertEqual(len(udh.ies), 2)
|
||||
ie = udh.ies[0]
|
||||
self.assertEqual(ie.iei, 0x71)
|
||||
self.assertEqual(ie.length, 0)
|
||||
self.assertEqual(ie.value, b'')
|
||||
ie = udh.ies[1]
|
||||
self.assertEqual(ie.iei, 0x70)
|
||||
self.assertEqual(ie.length, 1)
|
||||
self.assertEqual(ie.value, b'\xff')
|
||||
self.assertEqual(tail, b'\xab\xcd')
|
||||
|
||||
def test_to_bytes(self):
|
||||
indata = h2b('0571007001ff')
|
||||
udh, tail = UserDataHeader.from_bytes(indata)
|
||||
encoded = udh.to_bytes()
|
||||
self.assertEqual(encoded, indata)
|
||||
|
||||
class Test_AddressField(unittest.TestCase):
|
||||
def test_from_bytes(self):
|
||||
encoded = h2b('0480214399')
|
||||
af, trailer = AddressField.from_bytes(encoded)
|
||||
self.assertEqual(trailer, b'\x99')
|
||||
self.assertEqual(af.ton, 'unknown')
|
||||
self.assertEqual(af.npi, 'unknown')
|
||||
self.assertEqual(af.digits, '1234')
|
||||
|
||||
def test_from_bytes_odd(self):
|
||||
af, trailer = AddressField.from_bytes('038021f399')
|
||||
self.assertEqual(trailer, b'\x99')
|
||||
self.assertEqual(af.ton, 'unknown')
|
||||
self.assertEqual(af.npi, 'unknown')
|
||||
self.assertEqual(af.digits, '123')
|
||||
|
||||
def test_to_bytes(self):
|
||||
encoded = h2b('04802143')
|
||||
af, trailer = AddressField.from_bytes(encoded)
|
||||
self.assertEqual(af.to_bytes(), encoded)
|
||||
|
||||
def test_to_bytes_odd(self):
|
||||
af = AddressField('12345', 'international', 'isdn_e164')
|
||||
encoded = af.to_bytes()
|
||||
self.assertEqual(encoded, h2b('05912143f5'))
|
||||
|
||||
|
||||
class Test_SUBMIT(unittest.TestCase):
|
||||
def test_from_bytes(self):
|
||||
s = SMS_SUBMIT.from_bytes('550d0b911614261771f000f5a78c0b050423f423f40003010201424547494e3a56434152440d0a56455253494f4e3a322e310d0a4e3a4d650d0a54454c3b505245463b43454c4c3b564f4943453a2b36313431363237313137300d0a54454c3b484f4d453b564f4943453a2b36313339353337303437310d0a54454c3b574f524b3b564f4943453a2b36313339363734373031350d0a454e443a')
|
||||
self.assertEqual(s.tp_mti, 1)
|
||||
self.assertEqual(s.tp_rd, True)
|
||||
self.assertEqual(s.tp_vpf, 'relative')
|
||||
self.assertEqual(s.tp_rp, False)
|
||||
self.assertEqual(s.tp_udhi, True)
|
||||
self.assertEqual(s.tp_srr, False)
|
||||
self.assertEqual(s.tp_pid, 0)
|
||||
self.assertEqual(s.tp_dcs, 0xf5)
|
||||
self.assertEqual(s.tp_udl, 140)
|
||||
|
||||
class Test_DELIVER(unittest.TestCase):
|
||||
def test_from_bytes(self):
|
||||
d = SMS_DELIVER.from_bytes('0408D0E5759A0E7FF6907090307513000824010101BB400101')
|
||||
self.assertEqual(d.tp_mti, 0)
|
||||
self.assertEqual(d.tp_mms, True)
|
||||
self.assertEqual(d.tp_lp, False)
|
||||
self.assertEqual(d.tp_rp, False)
|
||||
self.assertEqual(d.tp_udhi, False)
|
||||
self.assertEqual(d.tp_sri, False)
|
||||
self.assertEqual(d.tp_pid, 0x7f)
|
||||
self.assertEqual(d.tp_dcs, 0xf6)
|
||||
self.assertEqual(d.tp_udl, 8)
|
||||
@@ -0,0 +1,145 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
# (C) 2022 by Harald Welte <laforge@osmocom.org>
|
||||
# All Rights Reserved
|
||||
#
|
||||
# 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
|
||||
from construct import Int8ub, GreedyBytes
|
||||
from pySim.tlv import *
|
||||
from pySim.utils import h2b
|
||||
|
||||
class TestUtils(unittest.TestCase):
|
||||
def test_camel_to_snake(self):
|
||||
cases = [
|
||||
('CamelCase', 'camel_case'),
|
||||
('CamelCaseUPPER', 'camel_case_upper'),
|
||||
('Camel_CASE_underSCORE', 'camel_case_under_score'),
|
||||
]
|
||||
for c in cases:
|
||||
self.assertEqual(camel_to_snake(c[0]), c[1])
|
||||
|
||||
def test_flatten_dict_lists(self):
|
||||
inp = [
|
||||
{ 'first': 1 },
|
||||
{ 'second': 2 },
|
||||
{ 'third': 3 },
|
||||
]
|
||||
out = { 'first': 1, 'second':2, 'third': 3}
|
||||
self.assertEqual(flatten_dict_lists(inp), out)
|
||||
|
||||
def test_flatten_dict_lists_nodict(self):
|
||||
inp = [
|
||||
{ 'first': 1 },
|
||||
{ 'second': 2 },
|
||||
{ 'third': 3 },
|
||||
4,
|
||||
]
|
||||
self.assertEqual(flatten_dict_lists(inp), inp)
|
||||
|
||||
def test_flatten_dict_lists_nested(self):
|
||||
inp = {'top': [
|
||||
{ 'first': 1 },
|
||||
{ 'second': 2 },
|
||||
{ 'third': 3 },
|
||||
] }
|
||||
out = {'top': { 'first': 1, 'second':2, 'third': 3 } }
|
||||
self.assertEqual(flatten_dict_lists(inp), out)
|
||||
|
||||
class TestTranscodable(unittest.TestCase):
|
||||
class XC_constr_class(Transcodable):
|
||||
_construct = Int8ub
|
||||
def __init__(self):
|
||||
super().__init__();
|
||||
|
||||
def test_XC_constr_class(self):
|
||||
"""Transcodable derived class with _construct class variable"""
|
||||
xc = TestTranscodable.XC_constr_class()
|
||||
self.assertEqual(xc.from_bytes(b'\x23'), 35)
|
||||
self.assertEqual(xc.to_bytes(), b'\x23')
|
||||
|
||||
class XC_constr_instance(Transcodable):
|
||||
def __init__(self):
|
||||
super().__init__();
|
||||
self._construct = Int8ub
|
||||
|
||||
def test_XC_constr_instance(self):
|
||||
"""Transcodable derived class with _construct instance variable"""
|
||||
xc = TestTranscodable.XC_constr_instance()
|
||||
self.assertEqual(xc.from_bytes(b'\x23'), 35)
|
||||
self.assertEqual(xc.to_bytes(), b'\x23')
|
||||
|
||||
class XC_method_instance(Transcodable):
|
||||
def __init__(self):
|
||||
super().__init__();
|
||||
def _from_bytes(self, do):
|
||||
return ('decoded', do)
|
||||
def _to_bytes(self):
|
||||
return self.decoded[1]
|
||||
|
||||
def test_XC_method_instance(self):
|
||||
"""Transcodable derived class with _{from,to}_bytes() methods"""
|
||||
xc = TestTranscodable.XC_method_instance()
|
||||
self.assertEqual(xc.to_bytes(), b'')
|
||||
self.assertEqual(xc.from_bytes(b''), None)
|
||||
self.assertEqual(xc.from_bytes(b'\x23'), ('decoded', b'\x23'))
|
||||
self.assertEqual(xc.to_bytes(), b'\x23')
|
||||
|
||||
class TestIE(unittest.TestCase):
|
||||
class MyIE(IE, tag=0x23, desc='My IE description'):
|
||||
_construct = Int8ub
|
||||
def to_ie(self):
|
||||
return self.to_bytes()
|
||||
|
||||
def test_IE_empty(self):
|
||||
ie = TestIE.MyIE()
|
||||
self.assertEqual(ie.to_dict(), {'my_ie': None})
|
||||
self.assertEqual(repr(ie), 'MyIE(None)')
|
||||
self.assertEqual(ie.is_constructed(), False)
|
||||
|
||||
def test_IE_from_bytes(self):
|
||||
ie = TestIE.MyIE()
|
||||
ie.from_bytes(b'\x42')
|
||||
self.assertEqual(ie.to_dict(), {'my_ie': 66})
|
||||
self.assertEqual(repr(ie), 'MyIE(66)')
|
||||
self.assertEqual(ie.is_constructed(), False)
|
||||
self.assertEqual(ie.to_bytes(), b'\x42')
|
||||
self.assertEqual(ie.to_ie(), b'\x42')
|
||||
|
||||
class TestCompact(unittest.TestCase):
|
||||
class IE_3(COMPACT_TLV_IE, tag=0x3):
|
||||
_construct = GreedyBytes
|
||||
class IE_7(COMPACT_TLV_IE, tag=0x7):
|
||||
_construct = GreedyBytes
|
||||
class IE_5(COMPACT_TLV_IE, tag=0x5):
|
||||
_construct = GreedyBytes
|
||||
# pylint: disable=undefined-variable
|
||||
class IE_Coll(TLV_IE_Collection, nested=[IE_3, IE_7, IE_5]):
|
||||
_construct = GreedyBytes
|
||||
def test_ATR(self):
|
||||
atr = h2b("31E073FE211F5745437531301365")
|
||||
c = self.IE_Coll()
|
||||
c.from_tlv(atr)
|
||||
self.assertEqual(c.children[0].tag, 3)
|
||||
self.assertEqual(c.children[0].to_bytes(), b'\xe0')
|
||||
self.assertEqual(c.children[1].tag, 7)
|
||||
self.assertEqual(c.children[1].to_bytes(), b'\xfe\x21\x1f')
|
||||
self.assertEqual(c.children[2].tag, 5)
|
||||
self.assertEqual(c.children[2].to_bytes(), b'\x45\x43\x75\x31\x30\x13\x65')
|
||||
self.assertEqual(c.to_tlv(), atr)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
Executable
+124
@@ -0,0 +1,124 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
# (C) 2023 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 pySim.utils import b2h, h2b, all_subclasses
|
||||
from pySim.tlv import *
|
||||
|
||||
import pySim.iso7816_4
|
||||
import pySim.ts_102_221
|
||||
import pySim.ts_102_222
|
||||
import pySim.ts_31_102
|
||||
import pySim.ts_31_103
|
||||
import pySim.ts_51_011
|
||||
import pySim.sysmocom_sja2
|
||||
import pySim.gsm_r
|
||||
import pySim.cdma_ruim
|
||||
import pySim.global_platform
|
||||
import pySim.global_platform.http
|
||||
|
||||
if 'unittest.util' in __import__('sys').modules:
|
||||
# Show full diff in self.assertEqual.
|
||||
__import__('sys').modules['unittest.util']._MAX_LENGTH = 999999999
|
||||
|
||||
def get_qualified_name(c):
|
||||
"""return the qualified (by module) name of a class."""
|
||||
return "%s.%s" % (c.__module__, c.__name__)
|
||||
|
||||
class TLV_IE_Test(unittest.TestCase):
|
||||
maxDiff = None
|
||||
|
||||
@classmethod
|
||||
def get_classes(cls):
|
||||
"""get list of TLV_IE sub-classes."""
|
||||
return all_subclasses(TLV_IE)
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
"""set-up method called once for this class by unittest framework"""
|
||||
cls.classes = cls.get_classes()
|
||||
|
||||
def test_decode_tlv(self):
|
||||
"""Test the decoder for a TLV_IE. Requires the given TLV_IE subclass
|
||||
to have a '_test_decode' attribute, containing a list of tuples. Each tuple
|
||||
is a 2-tuple (hexstring, decoded_dict).
|
||||
"""
|
||||
for c in self.classes:
|
||||
name = get_qualified_name(c)
|
||||
if hasattr(c, '_test_decode'):
|
||||
for t in c._test_decode:
|
||||
with self.subTest(name, test_decode=t):
|
||||
inst = c()
|
||||
encoded = t[0]
|
||||
decoded = { camel_to_snake(c.__name__): t[1] }
|
||||
context = t[2] if len(t) == 3 else {}
|
||||
logging.debug("Testing decode of %s", name)
|
||||
inst.from_tlv(h2b(encoded), context=context)
|
||||
re_dec = inst.to_dict()
|
||||
self.assertEqual(decoded, re_dec)
|
||||
|
||||
def test_encode_tlv(self):
|
||||
"""Test the encoder for a TLV_IE. Requires the given TLV_IE subclass
|
||||
to have a '_test_encode' attribute, containing a list of tuples. Each tuple
|
||||
is a 2-tuple (hexstring, decoded_dict).
|
||||
"""
|
||||
for c in self.classes:
|
||||
name = get_qualified_name(c)
|
||||
if hasattr(c, '_test_encode'):
|
||||
for t in c._test_encode:
|
||||
with self.subTest(name, test_encode=t):
|
||||
inst = c()
|
||||
encoded = t[0]
|
||||
decoded = { camel_to_snake(c.__name__): t[1] }
|
||||
context = t[2] if len(t) == 3 else {}
|
||||
logging.debug("Testing encode of %s", name)
|
||||
inst.from_dict(decoded)
|
||||
re_enc = b2h(inst.to_tlv(context))
|
||||
self.assertEqual(encoded.upper(), re_enc.upper())
|
||||
|
||||
def test_de_encode_tlv(self):
|
||||
"""Test the decoder and encoder for a TLV_IE. Performs first a decoder
|
||||
test, and then re-encodes the decoded data, comparing the re-encoded data with the
|
||||
initial input data.
|
||||
|
||||
Requires the given TLV_IE subclass to have a '_test_de_encode' attribute,
|
||||
containing a list of tuples. Each tuple is a 2-tuple (hexstring, decoded_dict).
|
||||
"""
|
||||
for c in self.classes:
|
||||
name = get_qualified_name(c)
|
||||
if hasattr(c, '_test_de_encode'):
|
||||
for t in c._test_de_encode:
|
||||
with self.subTest(name, test_de_encode=t):
|
||||
inst = c()
|
||||
encoded = t[0]
|
||||
decoded = { camel_to_snake(c.__name__): t[1] }
|
||||
context = t[2] if len(t) == 3 else {}
|
||||
logging.debug("Testing decode of %s", name)
|
||||
inst.from_tlv(h2b(encoded), context=context)
|
||||
re_dec = inst.to_dict()
|
||||
self.assertEqual(decoded, re_dec)
|
||||
logging.debug("Testing re-encode of %s", name)
|
||||
re_enc = b2h(inst.to_tlv(context=context))
|
||||
self.assertEqual(encoded.upper(), re_enc.upper())
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
logger = logging.getLogger()
|
||||
logger.setLevel(logging.DEBUG)
|
||||
unittest.main()
|
||||
Executable
+257
@@ -0,0 +1,257 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import unittest
|
||||
from pySim import utils
|
||||
from pySim.legacy import utils as legacy_utils
|
||||
from pySim.ts_31_102 import EF_SUCI_Calc_Info
|
||||
|
||||
# we don't really want to thest TS 102 221, but the underlying DataObject codebase
|
||||
from pySim.ts_102_221 import AM_DO_EF, AM_DO_DF, SC_DO
|
||||
|
||||
class DoTestCase(unittest.TestCase):
|
||||
|
||||
def testSeqOfChoices(self):
|
||||
"""A sequence of two choices with each a variety of DO/TLVs"""
|
||||
arr_seq = utils.DataObjectSequence('arr', sequence=[AM_DO_EF, SC_DO])
|
||||
# input data
|
||||
dec_in = [{'access_mode': ['update_erase', 'read_search_compare']}, {'control_reference_template':'PIN1'}]
|
||||
# encode it once
|
||||
encoded = arr_seq.encode(dec_in)
|
||||
# decode again
|
||||
re_decoded = arr_seq.decode(encoded)
|
||||
self.assertEqual(dec_in, re_decoded[0])
|
||||
|
||||
class DecTestCase(unittest.TestCase):
|
||||
# TS33.501 Annex C.4 test keys
|
||||
hnet_pubkey_profile_b = "0272DA71976234CE833A6907425867B82E074D44EF907DFB4B3E21C1C2256EBCD1" # ID 27 in test file
|
||||
hnet_pubkey_profile_a = "5A8D38864820197C3394B92613B20B91633CBD897119273BF8E4A6F4EEC0A650" # ID 30 in test file
|
||||
|
||||
# TS31.121 4.9.4 EF_SUCI_Calc_Info test file
|
||||
testfile_suci_calc_info = "A006020101020000A14B80011B8121" +hnet_pubkey_profile_b +"80011E8120" +hnet_pubkey_profile_a
|
||||
|
||||
decoded_testfile_suci = {
|
||||
'prot_scheme_id_list': [
|
||||
{'priority': 0, 'identifier': 2, 'key_index': 1},
|
||||
{'priority': 1, 'identifier': 1, 'key_index': 2},
|
||||
{'priority': 2, 'identifier': 0, 'key_index': 0}],
|
||||
'hnet_pubkey_list': [
|
||||
{'hnet_pubkey_identifier': 27, 'hnet_pubkey': hnet_pubkey_profile_b.lower()}, # because h2b/b2h returns all lower-case
|
||||
{'hnet_pubkey_identifier': 30, 'hnet_pubkey': hnet_pubkey_profile_a.lower()}]
|
||||
}
|
||||
|
||||
def testSplitHexStringToListOf5ByteEntries(self):
|
||||
input_str = "ffffff0003ffffff0002ffffff0001"
|
||||
expected = [
|
||||
"ffffff0003",
|
||||
"ffffff0002",
|
||||
"ffffff0001",
|
||||
]
|
||||
self.assertEqual(legacy_utils.hexstr_to_Nbytearr(input_str, 5), expected)
|
||||
|
||||
def testDecMCCfromPLMN(self):
|
||||
self.assertEqual(utils.dec_mcc_from_plmn("92f501"), 295)
|
||||
|
||||
def testDecMCCfromPLMN_unused(self):
|
||||
self.assertEqual(utils.dec_mcc_from_plmn("ff0f00"), 4095)
|
||||
|
||||
def testDecMCCfromPLMN_str(self):
|
||||
self.assertEqual(utils.dec_mcc_from_plmn_str("92f501"), "295")
|
||||
|
||||
def testDecMCCfromPLMN_unused_str(self):
|
||||
self.assertEqual(utils.dec_mcc_from_plmn_str("ff0f00"), "")
|
||||
|
||||
def testDecMNCfromPLMN_twoDigitMNC(self):
|
||||
self.assertEqual(utils.dec_mnc_from_plmn("92f501"), 10)
|
||||
|
||||
def testDecMNCfromPLMN_threeDigitMNC(self):
|
||||
self.assertEqual(utils.dec_mnc_from_plmn("031263"), 361)
|
||||
|
||||
def testDecMNCfromPLMN_unused(self):
|
||||
self.assertEqual(utils.dec_mnc_from_plmn("00f0ff"), 4095)
|
||||
|
||||
def testDecMNCfromPLMN_twoDigitMNC_str(self):
|
||||
self.assertEqual(utils.dec_mnc_from_plmn_str("92f501"), "10")
|
||||
|
||||
def testDecMNCfromPLMN_threeDigitMNC_str(self):
|
||||
self.assertEqual(utils.dec_mnc_from_plmn_str("031263"), "361")
|
||||
|
||||
def testDecMNCfromPLMN_unused_str(self):
|
||||
self.assertEqual(utils.dec_mnc_from_plmn_str("00f0ff"), "")
|
||||
|
||||
def test_enc_plmn(self):
|
||||
with self.subTest("2-digit MCC"):
|
||||
self.assertEqual(utils.enc_plmn("001", "01F"), "00F110")
|
||||
self.assertEqual(utils.enc_plmn("001", "01"), "00F110")
|
||||
self.assertEqual(utils.enc_plmn("295", "10"), "92F501")
|
||||
|
||||
with self.subTest("3-digit MCC"):
|
||||
self.assertEqual(utils.enc_plmn("001", "001"), "001100")
|
||||
self.assertEqual(utils.enc_plmn("302", "361"), "031263")
|
||||
|
||||
def testDecAct_noneSet(self):
|
||||
self.assertEqual(utils.dec_act("0000"), [])
|
||||
|
||||
def testDecAct_onlyUtran(self):
|
||||
self.assertEqual(utils.dec_act("8000"), ["UTRAN"])
|
||||
|
||||
def testDecAct_onlyEUtran(self):
|
||||
self.assertEqual(utils.dec_act("4000"), ["E-UTRAN NB-S1", "E-UTRAN WB-S1"])
|
||||
|
||||
def testDecAct_onlyNgRan(self):
|
||||
self.assertEqual(utils.dec_act("0800"), ["NG-RAN"])
|
||||
|
||||
def testDecAct_onlyGsm(self):
|
||||
self.assertEqual(utils.dec_act("0084"), ["GSM"])
|
||||
|
||||
def testDecAct_onlyGsmCompact(self):
|
||||
self.assertEqual(utils.dec_act("0040"), ["GSM COMPACT"])
|
||||
|
||||
def testDecAct_onlyCdma2000HRPD(self):
|
||||
self.assertEqual(utils.dec_act("0020"), ["cdma2000 HRPD"])
|
||||
|
||||
def testDecAct_onlyCdma20001xRTT(self):
|
||||
self.assertEqual(utils.dec_act("0010"), ["cdma2000 1xRTT"])
|
||||
|
||||
def testDecAct_allSet(self):
|
||||
self.assertEqual(utils.dec_act("ffff"), ['E-UTRAN NB-S1', 'E-UTRAN WB-S1', 'EC-GSM-IoT', 'GSM', 'GSM COMPACT', 'NG-RAN', 'UTRAN', 'cdma2000 1xRTT', 'cdma2000 HRPD'])
|
||||
|
||||
def testDecxPlmn_w_act(self):
|
||||
expected = {'mcc': '295', 'mnc': '10', 'act': ["UTRAN"]}
|
||||
self.assertEqual(utils.dec_xplmn_w_act("92f5018000"), expected)
|
||||
|
||||
def testFormatxPlmn_w_act(self):
|
||||
input_str = "92f501800092f5508000ffffff0000ffffff0000ffffff0000ffffff0000ffffff0000ffffff0000ffffff0000ffffff0000"
|
||||
expected = "\t92f5018000 # MCC: 295 MNC: 10 AcT: UTRAN\n"
|
||||
expected += "\t92f5508000 # MCC: 295 MNC: 05 AcT: UTRAN\n"
|
||||
expected += "\tffffff0000 # unused\n"
|
||||
expected += "\tffffff0000 # unused\n"
|
||||
expected += "\tffffff0000 # unused\n"
|
||||
expected += "\tffffff0000 # unused\n"
|
||||
expected += "\tffffff0000 # unused\n"
|
||||
expected += "\tffffff0000 # unused\n"
|
||||
expected += "\tffffff0000 # unused\n"
|
||||
expected += "\tffffff0000 # unused\n"
|
||||
self.assertEqual(legacy_utils.format_xplmn_w_act(input_str), expected)
|
||||
|
||||
|
||||
def testDecodeSuciCalcInfo(self):
|
||||
suci_calc_info = EF_SUCI_Calc_Info()
|
||||
decoded = suci_calc_info.decode_hex(self.testfile_suci_calc_info)
|
||||
self.assertDictEqual(self.decoded_testfile_suci, decoded)
|
||||
|
||||
def testEncodeSuciCalcInfo(self):
|
||||
suci_calc_info = EF_SUCI_Calc_Info()
|
||||
encoded = suci_calc_info.encode_hex(self.decoded_testfile_suci)
|
||||
self.assertEqual(encoded.lower(), self.testfile_suci_calc_info.lower())
|
||||
|
||||
def testEnc_msisdn(self):
|
||||
msisdn_encoded = utils.enc_msisdn("+4916012345678", npi=0x01, ton=0x03)
|
||||
self.assertEqual(msisdn_encoded, "0891946110325476f8ffffffffff")
|
||||
msisdn_encoded = utils.enc_msisdn("123456", npi=0x01, ton=0x03)
|
||||
self.assertEqual(msisdn_encoded, "04b1214365ffffffffffffffffff")
|
||||
msisdn_encoded = utils.enc_msisdn("12345678901234567890", npi=0x01, ton=0x03)
|
||||
self.assertEqual(msisdn_encoded, "0bb121436587092143658709ffff")
|
||||
msisdn_encoded = utils.enc_msisdn("+12345678901234567890", npi=0x01, ton=0x03)
|
||||
self.assertEqual(msisdn_encoded, "0b9121436587092143658709ffff")
|
||||
msisdn_encoded = utils.enc_msisdn("", npi=0x01, ton=0x03)
|
||||
self.assertEqual(msisdn_encoded, "ffffffffffffffffffffffffffff")
|
||||
msisdn_encoded = utils.enc_msisdn("+", npi=0x01, ton=0x03)
|
||||
self.assertEqual(msisdn_encoded, "ffffffffffffffffffffffffffff")
|
||||
|
||||
def testDec_msisdn(self):
|
||||
msisdn_decoded = utils.dec_msisdn("0891946110325476f8ffffffffff")
|
||||
self.assertEqual(msisdn_decoded, (1, 1, "+4916012345678"))
|
||||
msisdn_decoded = utils.dec_msisdn("04b1214365ffffffffffffffffff")
|
||||
self.assertEqual(msisdn_decoded, (1, 3, "123456"))
|
||||
msisdn_decoded = utils.dec_msisdn("0bb121436587092143658709ffff")
|
||||
self.assertEqual(msisdn_decoded, (1, 3, "12345678901234567890"))
|
||||
msisdn_decoded = utils.dec_msisdn("ffffffffffffffffffffffffffff")
|
||||
self.assertEqual(msisdn_decoded, None)
|
||||
msisdn_decoded = utils.dec_msisdn("00112233445566778899AABBCCDDEEFF001122330bb121436587092143658709ffff")
|
||||
self.assertEqual(msisdn_decoded, (1, 3, "12345678901234567890"))
|
||||
msisdn_decoded = utils.dec_msisdn("ffffffffffffffffffffffffffffffffffffffff0bb121436587092143658709ffff")
|
||||
self.assertEqual(msisdn_decoded, (1, 3, "12345678901234567890"))
|
||||
|
||||
class TestBerTlv(unittest.TestCase):
|
||||
def test_BerTlvTagDec(self):
|
||||
res = utils.bertlv_parse_tag(b'\x01')
|
||||
self.assertEqual(res, ({'tag':1, 'constructed':False, 'class': 0}, b''))
|
||||
res = utils.bertlv_parse_tag(b'\x21')
|
||||
self.assertEqual(res, ({'tag':1, 'constructed':True, 'class': 0}, b''))
|
||||
res = utils.bertlv_parse_tag(b'\x81\x23')
|
||||
self.assertEqual(res, ({'tag':1, 'constructed':False, 'class': 2}, b'\x23'))
|
||||
res = utils.bertlv_parse_tag(b'\x1f\x8f\x00\x23')
|
||||
self.assertEqual(res, ({'tag':0xf<<7, 'constructed':False, 'class': 0}, b'\x23'))
|
||||
|
||||
def test_BerTlvLenDec(self):
|
||||
self.assertEqual(utils.bertlv_encode_len(1), b'\x01')
|
||||
self.assertEqual(utils.bertlv_encode_len(127), b'\x7f')
|
||||
self.assertEqual(utils.bertlv_encode_len(128), b'\x81\x80')
|
||||
self.assertEqual(utils.bertlv_encode_len(0x123456), b'\x83\x12\x34\x56')
|
||||
|
||||
def test_BerTlvLenEnc(self):
|
||||
self.assertEqual(utils.bertlv_parse_len(b'\x01\x23'), (1, b'\x23'))
|
||||
self.assertEqual(utils.bertlv_parse_len(b'\x7f'), (127, b''))
|
||||
self.assertEqual(utils.bertlv_parse_len(b'\x81\x80'), (128, b''))
|
||||
self.assertEqual(utils.bertlv_parse_len(b'\x83\x12\x34\x56\x78'), (0x123456, b'\x78'))
|
||||
|
||||
def test_BerTlvParseOne(self):
|
||||
res = utils.bertlv_parse_one(b'\x81\x01\x01');
|
||||
self.assertEqual(res, ({'tag':1, 'constructed':False, 'class':2}, 1, b'\x01', b''))
|
||||
|
||||
class TestComprTlv(unittest.TestCase):
|
||||
def test_ComprTlvTagDec(self):
|
||||
res = utils.comprehensiontlv_parse_tag(b'\x12\x23')
|
||||
self.assertEqual(res, ({'tag': 0x12, 'comprehension': False}, b'\x23'))
|
||||
res = utils.comprehensiontlv_parse_tag(b'\x92')
|
||||
self.assertEqual(res, ({'tag': 0x12, 'comprehension': True}, b''))
|
||||
res = utils.comprehensiontlv_parse_tag(b'\x7f\x12\x34')
|
||||
self.assertEqual(res, ({'tag': 0x1234, 'comprehension': False}, b''))
|
||||
res = utils.comprehensiontlv_parse_tag(b'\x7f\x82\x34\x56')
|
||||
self.assertEqual(res, ({'tag': 0x234, 'comprehension': True}, b'\x56'))
|
||||
|
||||
def test_ComprTlvTagEnc(self):
|
||||
res = utils.comprehensiontlv_encode_tag(0x12)
|
||||
self.assertEqual(res, b'\x12')
|
||||
res = utils.comprehensiontlv_encode_tag({'tag': 0x12})
|
||||
self.assertEqual(res, b'\x12')
|
||||
res = utils.comprehensiontlv_encode_tag({'tag': 0x12, 'comprehension':True})
|
||||
self.assertEqual(res, b'\x92')
|
||||
res = utils.comprehensiontlv_encode_tag(0x1234)
|
||||
self.assertEqual(res, b'\x7f\x12\x34')
|
||||
res = utils.comprehensiontlv_encode_tag({'tag': 0x1234, 'comprehension':True})
|
||||
self.assertEqual(res, b'\x7f\x92\x34')
|
||||
|
||||
class TestDgiTlv(unittest.TestCase):
|
||||
def test_DgiTlvLenEnc(self):
|
||||
self.assertEqual(utils.dgi_encode_len(10), b'\x0a')
|
||||
self.assertEqual(utils.dgi_encode_len(254), b'\xfe')
|
||||
self.assertEqual(utils.dgi_encode_len(255), b'\xff\x00\xff')
|
||||
self.assertEqual(utils.dgi_encode_len(65535), b'\xff\xff\xff')
|
||||
with self.assertRaises(ValueError):
|
||||
utils.dgi_encode_len(65536)
|
||||
|
||||
def testDgiTlvLenDec(self):
|
||||
self.assertEqual(utils.dgi_parse_len(b'\x0a\x0b'), (10, b'\x0b'))
|
||||
self.assertEqual(utils.dgi_parse_len(b'\xfe\x0b'), (254, b'\x0b'))
|
||||
self.assertEqual(utils.dgi_parse_len(b'\xff\x00\xff\x0b'), (255, b'\x0b'))
|
||||
|
||||
class TestLuhn(unittest.TestCase):
|
||||
def test_verify(self):
|
||||
utils.verify_luhn('8988211000000530082')
|
||||
|
||||
def test_encode(self):
|
||||
self.assertEqual(utils.calculate_luhn('898821100000053008'), 2)
|
||||
|
||||
def test_sanitize_iccid(self):
|
||||
# 19 digits with correct luhn; we expect no change
|
||||
self.assertEqual(utils.sanitize_iccid('8988211000000530082'), '8988211000000530082')
|
||||
# 20 digits with correct luhn; we expect no change
|
||||
self.assertEqual(utils.sanitize_iccid('89882110000005300811'), '89882110000005300811')
|
||||
# 19 digits without correct luhn; we expect check digit to be added
|
||||
self.assertEqual(utils.sanitize_iccid('8988211000000530081'), '89882110000005300811')
|
||||
# 18 digits; we expect luhn check digit to be added
|
||||
self.assertEqual(utils.sanitize_iccid('898821100000053008'), '8988211000000530082')
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
Reference in new issue
Block a user