From 1f7a9bd5b40da67d2d8fbccb190d86c50db241d5 Mon Sep 17 00:00:00 2001 From: Harald Welte Date: Sun, 14 Jan 2024 10:09:07 +0100 Subject: [PATCH] TLV: Add DGI encoding of "GP Scripting Language Annex B" The DGI encoding is specified in Annex B of the "GlobalPlatform Systems Scripting Language Specification v1.1.0" which is an "archived" specification that is no longer published by GlobalPlatform, despite it being referenced from the GlobalPlatform Card Specification v2.3, which is the basis of the GSMA eSIM specifications. For some reason it was the belief of the specification authors that yet another format of TLV encoding is needed, in addition to the BER-TLV and COMPREHENSION-TLV used by the very same specifications. The encoding of the tag is not really specified anywhere, but I've only seen 16-bit examples. The encoding of the length is specified and implemented here accordingly. Change-Id: Ie29ab7eb39f3165f3d695fcc1f02051338095697 --- pySim/tlv.py | 22 ++++++++++++++++++++++ pySim/utils.py | 36 ++++++++++++++++++++++++++++++++++++ tests/test_utils.py | 14 ++++++++++++++ 3 files changed, 72 insertions(+) diff --git a/pySim/tlv.py b/pySim/tlv.py index 826f02f3..31051aaa 100644 --- a/pySim/tlv.py +++ b/pySim/tlv.py @@ -24,6 +24,7 @@ from construct import * from pySim.utils import bertlv_encode_len, bertlv_parse_len, bertlv_encode_tag, bertlv_parse_tag from pySim.utils import comprehensiontlv_encode_tag, comprehensiontlv_parse_tag from pySim.utils import bertlv_parse_tag_raw, comprehensiontlv_parse_tag_raw +from pySim.utils import dgi_parse_tag_raw, dgi_parse_len, dgi_encode_tag, dgi_encode_len from pySim.construct import build_construct, parse_construct, LV, HexAdapter, BcdAdapter, BitsRFU, GsmStringAdapter from pySim.exceptions import * @@ -302,6 +303,27 @@ class COMPR_TLV_IE(TLV_IE): return bertlv_encode_len(len(val)) +class DGI_TLV_IE(TLV_IE): + """TLV_IE formated as GlobalPlatform Systems Scripting Language Specification v1.1.0 Annex B.""" + + def __init__(self, **kwargs): + super().__init__(**kwargs) + + @classmethod + def _parse_tag_raw(cls, do: bytes) -> Tuple[int, bytes]: + return dgi_parse_tag_raw(do) + + @classmethod + def _parse_len(cls, do: bytes) -> Tuple[int, bytes]: + return dgi_parse_len(do) + + def _encode_tag(self) -> bytes: + return dgi_encode_tag(self._compute_tag()) + + def _encode_len(self, val: bytes) -> bytes: + return dgi_encode_len(len(val)) + + class TLV_IE_Collection(metaclass=TlvCollectionMeta): # we specify the metaclass so any downstream subclasses will automatically use it """A TLV_IE_Collection consists of multiple TLV_IE classes identified by their tags. diff --git a/pySim/utils.py b/pySim/utils.py index 6523d985..581abf28 100644 --- a/pySim/utils.py +++ b/pySim/utils.py @@ -358,6 +358,42 @@ def bertlv_parse_one(binary: bytes) -> Tuple[dict, int, bytes, bytes]: return (tagdict, length, value, remainder) +def dgi_parse_tag_raw(binary: bytes) -> Tuple[int, bytes]: + # In absence of any clear spec guidance we assume it's always 16 bit + return int.from_bytes(binary[:2], 'big'), binary[2:] + +def dgi_encode_tag(t: int) -> bytes: + return t.to_bytes(2, 'big') + +def dgi_encode_len(length: int) -> bytes: + """Encode a single Length value according to GlobalPlatform Systems Scripting Language + Specification v1.1.0 Annex B. + Args: + length : length value to be encoded + Returns: + binary output data of encoded length field + """ + if length < 255: + return length.to_bytes(1, 'big') + elif length <= 0xffff: + return b'\xff' + length.to_bytes(2, 'big') + else: + raise ValueError("Length > 32bits not supported") + +def dgi_parse_len(binary: bytes) -> Tuple[int, bytes]: + """Parse a single Length value according to GlobalPlatform Systems Scripting Language + Specification v1.1.0 Annex B. + Args: + binary : binary input data of BER-TLV length field + Returns: + Tuple of (length, remainder) + """ + if binary[0] == 255: + assert len(binary) >= 3 + return ((binary[1] << 8) | binary[2]), binary[3:] + else: + return binary[0], binary[1:] + # IMSI encoded format: # For IMSI 0123456789ABCDE: # diff --git a/tests/test_utils.py b/tests/test_utils.py index ea1964b9..7609f805 100755 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -222,5 +222,19 @@ class TestComprTlv(unittest.TestCase): 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')) + if __name__ == "__main__": unittest.main()