laforge has uploaded this change for review. ( 
https://gerrit.osmocom.org/c/pysim/+/35806?usp=email )


Change subject: construct: Add StripTrailerAdapter
......................................................................

construct: Add StripTrailerAdapter

In smart cards, we every so often encounter data types that contain a
bit-mask whose length depends on whether or not there are any of the
least-significant bits are set.  So far we worked around this with
some kind of Struct('byte1', 'byte2'/COptional, 'byte3'/COptional)
approach.

Let's do thisin a generic way using the new StripTrailerAdapter.

Change-Id: I659aa7247c57c680895b0bf8412f9e477fc3587d
---
M pySim/construct.py
M pySim/global_platform/__init__.py
M tests/test_construct.py
3 files changed, 97 insertions(+), 25 deletions(-)



  git pull ssh://gerrit.osmocom.org:29418/pysim refs/changes/06/35806/1

diff --git a/pySim/construct.py b/pySim/construct.py
index 988dbf3..8ef7941 100644
--- a/pySim/construct.py
+++ b/pySim/construct.py
@@ -365,6 +365,37 @@
         ia = ipaddress.IPv6Address(obj)
         return ia.packed

+class StripTrailerAdapter(Adapter):
+    """
+    Encoder removes all trailing bytes matching the default_value
+    Decoder pads input data up to total_length with default_value
+
+    This is used in constellations like 
"FlagsEnum(StripTrailerAdapter(GreedyBytes, 3), ..."
+    where you have a bit-mask that may have 1, 2 or 3 bytes, depending on 
whether or not any
+    of the LSBs are actually set.
+    """
+    def __init__(self, subcon, total_length:int, default_value=b'\x00', 
min_len=1):
+        super().__init__(subcon)
+        assert len(default_value) == 1
+        self.total_length = total_length
+        self.default_value = default_value
+        self.min_len = min_len
+
+    def _decode(self, obj, context, path):
+        assert type(obj) == bytes
+        # pad with suppressed/missing bytes
+        if len(obj) < self.total_length:
+            obj += self.default_value * (self.total_length - len(obj))
+        return int.from_bytes(obj, 'big')
+
+    def _encode(self, obj, context, path):
+        assert type(obj) == int
+        obj = obj.to_bytes(self.total_length, 'big')
+        # remove trailing bytes if they are zero
+        while len(obj) > self.min_len and obj[-1] == self.default_value[0]:
+            obj = obj[:-1]
+        return obj
+

 def filter_dict(d, exclude_prefix='_'):
     """filter the input dict to ensure no keys starting with 'exclude_prefix' 
remain."""
diff --git a/pySim/global_platform/__init__.py 
b/pySim/global_platform/__init__.py
index 494cfe1..7b5ab8f 100644
--- a/pySim/global_platform/__init__.py
+++ b/pySim/global_platform/__init__.py
@@ -124,15 +124,16 @@
     pass

 # GP v2.3 11.1.9
-KeyUsageQualifier = Struct('byte1'/FlagsEnum(Byte, 
verification_encryption=0x80,
-                                             computation_decipherment=0x40,
-                                             sm_response=0x20,
-                                             sm_command=0x10,
-                                             confidentiality=0x08,
-                                             crypto_checksum=0x04,
-                                             digital_signature=0x02,
-                                             crypto_authorization=0x01),
-                           'byte2'/COptional(FlagsEnum(Byte, 
key_agreement=0x80)))
+KeyUsageQualifier = FlagsEnum(StripTrailerAdapter(GreedyBytes, 2),
+                              verification_encryption=0x8000,
+                              computation_decipherment=0x4000,
+                              sm_response=0x2000,
+                              sm_command=0x1000,
+                              confidentiality=0x0800,
+                              crypto_checksum=0x0400,
+                              digital_signature=0x0200,
+                              crypto_authorization=0x0100,
+                              key_agreement=0x0080)

 # GP v2.3 11.1.10
 KeyAccess = Enum(Byte, sd_and_any_assoc_app=0x00, sd_only=0x01, 
any_assoc_app_but_not_sd=0x02,
@@ -210,13 +211,13 @@
 class CiphersForLFDBEncryption(BER_TLV_IE, tag=0x84):
     _construct = Enum(Byte, tripledes16=0x01, aes128=0x02, aes192=0x04, 
aes256=0x08,
                       icv_supported_for_lfdb=0x80)
-CipherSuitesForSignatures = Struct('byte1'/FlagsEnum(Byte, 
rsa1024_pkcsv15_sha1=0x01,
-                                                     
rsa_gt1024_pss_sha256=0x02,
-                                                     
single_des_plus_final_triple_des_mac_16b=0x04,
-                                                     cmac_aes128=0x08, 
cmac_aes192=0x10, cmac_aes256=0x20,
-                                                     ecdsa_ecc256_sha256=0x40, 
ecdsa_ecc384_sha384=0x80),
-                                   'byte2'/COptional(FlagsEnum(Byte, 
ecdsa_ecc512_sha512=0x01,
-                                                               
ecdsa_ecc_521_sha512=0x02)))
+CipherSuitesForSignatures = FlagsEnum(StripTrailerAdapter(GreedyBytes, 2),
+                                      rsa1024_pkcsv15_sha1=0x0100,
+                                      rsa_gt1024_pss_sha256=0x0200,
+                                      
single_des_plus_final_triple_des_mac_16b=0x0400,
+                                      cmac_aes128=0x0800, cmac_aes192=0x1000, 
cmac_aes256=0x2000,
+                                      ecdsa_ecc256_sha256=0x4000, 
ecdsa_ecc384_sha384=0x8000,
+                                      ecdsa_ecc512_sha512=0x0001, 
ecdsa_ecc_521_sha512=0x0002)
 class CiphersForTokens(BER_TLV_IE, tag=0x85):
     _construct = CipherSuitesForSignatures
 class CiphersForReceipts(BER_TLV_IE, tag=0x86):
@@ -387,15 +388,16 @@

 # Section 11.4.3.1 Table 11-36 + Section 11.1.2
 class Privileges(BER_TLV_IE, tag=0xc5):
-    _construct = Struct('byte1'/FlagsEnum(Byte, security_domain=0x80, 
dap_verification=0x40,
-                                          delegated_management=0x20, 
card_lock=0x10, card_terminate=0x08,
-                                          card_reset=0x04, cvm_management=0x02,
-                                          mandated_dap_verification=0x01),
-                        'byte2'/COptional(FlagsEnum(Byte, trusted_path=0x80, 
authorized_management=0x40,
-                                          token_management=0x20, 
global_delete=0x10, global_lock=0x08,
-                                          global_registry=0x04, 
final_application=0x02, global_service=0x01)),
-                        'byte3'/COptional(FlagsEnum(Byte, 
receipt_generation=0x80, ciphered_load_file_data_block=0x40,
-                                          contactless_activation=0x20, 
contactless_self_activation=0x10)))
+    _construct = 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)

 # Section 11.4.3.1 Table 11-36 + Section 11.1.7
 class ImplicitSelectionParameter(BER_TLV_IE, tag=0xcf):
diff --git a/tests/test_construct.py b/tests/test_construct.py
index 11822a8..6251a14 100644
--- a/tests/test_construct.py
+++ b/tests/test_construct.py
@@ -1,7 +1,9 @@
 #!/usr/bin/env python3

 import unittest
+from pySim.utils import b2h, h2b
 from pySim.construct import *
+from construct import FlagsEnum

 tests = [
         ( b'\x80', 0x80 ),
@@ -75,6 +77,26 @@
             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__":

--
To view, visit https://gerrit.osmocom.org/c/pysim/+/35806?usp=email
To unsubscribe, or for help writing mail filters, visit 
https://gerrit.osmocom.org/settings

Gerrit-Project: pysim
Gerrit-Branch: master
Gerrit-Change-Id: I659aa7247c57c680895b0bf8412f9e477fc3587d
Gerrit-Change-Number: 35806
Gerrit-PatchSet: 1
Gerrit-Owner: laforge <[email protected]>
Gerrit-MessageType: newchange

Reply via email to