feat(iatverifier): add type indicator for CCA tokens

Change-Id: I0cf97e5c2e9eb639f0d7a5c9163f3d9765c47112
Signed-off-by: Mate Toth-Pal <mate.toth-pal@arm.com>
diff --git a/iat-verifier/iatverifier/attest_token_verifier.py b/iat-verifier/iatverifier/attest_token_verifier.py
index beed405..16c94a5 100644
--- a/iat-verifier/iatverifier/attest_token_verifier.py
+++ b/iat-verifier/iatverifier/attest_token_verifier.py
@@ -1,5 +1,5 @@
 # -----------------------------------------------------------------------------
-# Copyright (c) 2019-2022, Arm Limited. All rights reserved.
+# Copyright (c) 2019-2025, Arm Limited. All rights reserved.
 #
 # SPDX-License-Identifier: BSD-3-Clause
 #
@@ -554,6 +554,13 @@
         hmac_msg = Mac0Message(payload=token, key=key, phdr=p_header)
         return hmac_msg.encode()
 
+    def check_type_indicator(self, *, token):
+        # By default no type indication is expected
+        return token
+
+    def encode_type_indicator(self, *, encoder):
+        # By default no type indication is added
+        pass
 
     def _get_cose_sign1_payload(self, cose, *, verify_signature):
         msg = Sign1Message.decode(cose)
@@ -628,13 +635,25 @@
             # Sign and pack in a COSE envelope if necessary
             signed_token = self._sign_token(token)
 
-            # Pack as a bstr if necessary
-            if root:
-                token_encoder.write(signed_token)
-            else:
+            self.encode_type_indicator(encoder=token_encoder)
+
+            if 'encode_type_indicator' in self.__dict__:
+                # If the current verifier has its own encode_type_indicator
+                # implemented, then the signed token needs to be encoded as a
+                # bytestring, and passed to the type indicator encoding like
+                # that:
                 token_encoder.encode_bytestring(signed_token)
+            else:
+                # Otherwise encoding depends on the 'root' parameter
+                if root:
+                    token_encoder.write(signed_token)
+                else:
+                    token_encoder.encode_bytestring(signed_token)
 
     def parse_token(self, *, token, lower_case_key):
+
+        token = self.check_type_indicator(token=token)
+
         if self._get_method() == AttestationTokenVerifier.SIGN_METHOD_RAW:
             payload = token
             protected_header = None
diff --git a/iat-verifier/iatverifier/cca_token_verifier.py b/iat-verifier/iatverifier/cca_token_verifier.py
index 8531896..940af76 100644
--- a/iat-verifier/iatverifier/cca_token_verifier.py
+++ b/iat-verifier/iatverifier/cca_token_verifier.py
@@ -1,10 +1,12 @@
 # -----------------------------------------------------------------------------
-# Copyright (c) 2022, Arm Limited. All rights reserved.
+# Copyright (c) 2022-2025, Arm Limited. All rights reserved.
 #
 # SPDX-License-Identifier: BSD-3-Clause
 #
 # -----------------------------------------------------------------------------
 
+import cbor2
+
 from cryptography.hazmat.primitives import hashes
 from pycose.headers import Algorithm
 from pycose.algorithms import Es256, Es384, Es512
@@ -27,6 +29,7 @@
 from iatverifier.psa_iot_profile1_token_claims import SWComponentTypeClaim, SwComponentVersionClaim
 from iatverifier.psa_iot_profile1_token_claims import MeasurementValueClaim, SignerIdClaim
 from iatverifier.util import ec2_cose_key_from_raw_ecdsa
+from iatverifier.attest_token_verifier import _CBOR_MAJOR_TYPE_ARRAY
 
 _algorithms = {
     Es256: 65,
@@ -34,6 +37,29 @@
     Es512: 133,
 }
 
+COAP_CONTENT_INDICATOR = 263
+
+def cca_check_type_indicator(verifier, token):
+    if (isinstance(token, bytes)):
+        # It can happen that the token is not a map structure, but an encoded cbor.
+        # try to parse it:
+        token = cbor2.loads(token)
+    if not isinstance(token, list):
+        msg = f'Expecting a type indicator (token must be a list)'
+        verifier.error(msg)
+    type_indicator_value = token[0]
+    if not isinstance(type_indicator_value, int):
+        msg = f'Expecting a type indicator value is not an int'
+        verifier.error(msg)
+    if type_indicator_value != COAP_CONTENT_INDICATOR:
+        msg = f'Invalid type indicator: {type_indicator_value}'
+        verifier.error(msg)
+    return token[1]
+
+def cca_encode_type_indicator(verifier, encoder):
+    encoder.encode_length(_CBOR_MAJOR_TYPE_ARRAY, 2)
+    encoder.encode_int(COAP_CONTENT_INDICATOR)
+
 class CCATokenVerifier(Verifier):
 
     def get_claim_key(self=None):
@@ -46,7 +72,7 @@
         return {Algorithm: self._get_cose_alg()}
 
     def _get_wrapping_tag(self):
-        return 399
+        return 907
 
     def _parse_p_header(self, msg):
         alg = self._get_cose_alg()
@@ -150,7 +176,7 @@
         if alg != msg_alg:
             raise ValueError('Unexpected alg in protected header (expected {} instead of {})'.format(alg, msg_alg))
 
-    def __init__(self, *, method, cose_alg, signing_key=None, configuration, necessity):
+    def __init__(self, *, method, cose_alg, signing_key=None, configuration, necessity, has_type_indicator=True):
         verifier_claims= [
             (CCARealmChallengeClaim, {'verifier':self, 'expected_challenge_byte': None, 'necessity': Claim.MANDATORY}),
             (CCARealmPersonalizationValue, {'verifier':self, 'necessity': Claim.MANDATORY}),
@@ -171,6 +197,10 @@
             cose_alg=cose_alg,
             signing_key=signing_key)
 
+        if has_type_indicator:
+            self.check_type_indicator = lambda token: cca_check_type_indicator(self, token)
+            self.encode_type_indicator = lambda encoder: cca_encode_type_indicator(self, encoder)
+
     def verify(self, token_item):
         # Realm token was not checked against the realm public key as it needs
         # to be extracted from the realm token itself.
@@ -258,7 +288,7 @@
         if alg != msg_alg:
             raise ValueError('Unexpected alg in protected header (expected {} instead of {})'.format(alg, msg_alg))
 
-    def __init__(self, *, method, cose_alg, signing_key, configuration, necessity):
+    def __init__(self, *, method, cose_alg, signing_key, configuration, necessity, has_type_indicator=True):
 
         # First prepare the claim hierarchy for this token
 
@@ -290,3 +320,8 @@
             method=method,
             cose_alg=cose_alg,
             signing_key=signing_key)
+
+        if has_type_indicator:
+            self.check_type_indicator = lambda token: cca_check_type_indicator(self, token)
+            self.encode_type_indicator = lambda encoder: cca_encode_type_indicator(self, encoder)
+
diff --git a/iat-verifier/scripts/check_iat b/iat-verifier/scripts/check_iat
index 5cd1e81..6ae45cc 100755
--- a/iat-verifier/scripts/check_iat
+++ b/iat-verifier/scripts/check_iat
@@ -1,6 +1,6 @@
 #!/usr/bin/env python3
 # -----------------------------------------------------------------------------
-# Copyright (c) 2019-2022, Arm Limited. All rights reserved.
+# Copyright (c) 2019-2025, Arm Limited. All rights reserved.
 #
 # SPDX-License-Identifier: BSD-3-Clause
 #
@@ -69,6 +69,9 @@
                         help='''The type of the Token.''',
                         choices=token_verifiers.keys(),
                         required=True)
+    parser.add_argument('--expect-token-indicator',
+                        help='''Expect token indicator in the cbor.''',
+                        action='store_true')
 
     args = parser.parse_args()
 
@@ -127,7 +130,8 @@
             cose_alg=cose_alg,
             signing_key=key,
             configuration=config,
-            necessity=None)
+            necessity=None,
+            has_type_indicator=args.expect_token_indicator)
     elif verifier_class == PSA_2_0_0_TokenVerifier:
         key_checked = args.key
         key = read_keyfile(keyfile=args.key, method=method)
diff --git a/iat-verifier/scripts/compile_token b/iat-verifier/scripts/compile_token
index 34ec781..9195712 100755
--- a/iat-verifier/scripts/compile_token
+++ b/iat-verifier/scripts/compile_token
@@ -1,6 +1,6 @@
 #!/usr/bin/env python3
 #-------------------------------------------------------------------------------
-# Copyright (c) 2019-2022, Arm Limited. All rights reserved.
+# Copyright (c) 2019-2025, Arm Limited. All rights reserved.
 #
 # SPDX-License-Identifier: BSD-3-Clause
 #
@@ -59,6 +59,9 @@
                         help='''The type of the Token.''',
                         choices=token_verifiers.keys(),
                         required=True)
+    parser.add_argument('--gen-token-indicator',
+                        help='''Expect token indicator in the cbor.''',
+                        action='store_true')
 
     args = parser.parse_args()
 
@@ -114,7 +117,8 @@
             cose_alg=cose_alg,
             signing_key=key,
             configuration=configuration,
-            necessity=None)
+            necessity=None,
+            has_type_indicator=args.gen_token_indicator)
     elif verifier_class == PSA_2_0_0_TokenVerifier:
         key_checked = args.key
         key = read_keyfile(keyfile=args.key, method=METHOD)
diff --git a/iat-verifier/scripts/decompile_token b/iat-verifier/scripts/decompile_token
index 6bcb26f..59c88b1 100755
--- a/iat-verifier/scripts/decompile_token
+++ b/iat-verifier/scripts/decompile_token
@@ -1,6 +1,6 @@
 #!/usr/bin/env python3
 #-------------------------------------------------------------------------------
-# Copyright (c) 2019-2022, Arm Limited. All rights reserved.
+# Copyright (c) 2019-2025, Arm Limited. All rights reserved.
 #
 # SPDX-License-Identifier: BSD-3-Clause
 #
@@ -39,6 +39,9 @@
                         help='''The type of the Token.''',
                         choices=token_verifiers.keys(),
                         required=True)
+    parser.add_argument('--expect-token-indicator',
+                        help='''Expect token indicator in the cbor.''',
+                        action='store_true')
     args = parser.parse_args()
 
     verifier_class = token_verifiers[args.token_type]
@@ -67,7 +70,8 @@
             cose_alg=cose_alg,
             signing_key=None,
             configuration=None,
-            necessity=None)
+            necessity=None,
+            has_type_indicator=args.expect_token_indicator)
     elif verifier_class == PSA_2_0_0_TokenVerifier:
         verifier = PSA_2_0_0_TokenVerifier(
             method=AttestationTokenVerifier.SIGN_METHOD_SIGN1,
diff --git a/iat-verifier/tests/data/cca_token.cbor b/iat-verifier/tests/data/cca_token.cbor
index e40f8b7..346c17e 100644
--- a/iat-verifier/tests/data/cca_token.cbor
+++ b/iat-verifier/tests/data/cca_token.cbor
Binary files differ
diff --git a/iat-verifier/tests/test_verifier.py b/iat-verifier/tests/test_verifier.py
index 17658f1..7ded05d 100644
--- a/iat-verifier/tests/test_verifier.py
+++ b/iat-verifier/tests/test_verifier.py
@@ -1,5 +1,5 @@
 # -----------------------------------------------------------------------------
-# Copyright (c) 2019-2024, Arm Limited. All rights reserved.
+# Copyright (c) 2019-2025, Arm Limited. All rights reserved.
 #
 # SPDX-License-Identifier: BSD-3-Clause
 #
@@ -114,7 +114,19 @@
                 cose_alg=Es384,
                 signing_key=platform_token_key,
                 configuration=self.config,
-                necessity=AttestationClaim.MANDATORY))
+                necessity=AttestationClaim.MANDATORY,
+                has_type_indicator=False))
+
+        create_and_read_iat(
+            DATA_DIR,
+            'cca_platform_token.yaml',
+            CCAPlatformTokenVerifier(
+                method=method,
+                cose_alg=Es384,
+                signing_key=platform_token_key,
+                configuration=self.config,
+                necessity=AttestationClaim.MANDATORY,
+                has_type_indicator=True))
 
         with self.assertRaises(ValueError) as test_ctx:
             create_and_read_iat(