Refactor token parsing to keep data in a tree

Refactor token parsing to keep data in a tree of TokenItem objects.
For each data item the Claim type is stored.

Change-Id: I8bd33b51b7fb8ff86bfdce8ffe3c0c7f9bac5895
Signed-off-by: Mate Toth-Pal <mate.toth-pal@arm.com>
diff --git a/iat-verifier/README.rst b/iat-verifier/README.rst
index a7c00ca..9c26b78 100644
--- a/iat-verifier/README.rst
+++ b/iat-verifier/README.rst
@@ -244,24 +244,34 @@
    * For each claim a new class must be created that inherits from
      ``AttestationClaim`` or from one of its descendants
 
-   * ``NonVerifiedClaim`` and ``CompositeAttestClaim`` are descendants of
-     ``AttestationClaim``, for details on how to use them see the documentation
-     in the class definition.
+   * ``CompositeAttestClaim`` is descendants of ``AttestationClaim``, for
+     details on how to use it see the documentation in the class definition.
 
-   * For each claim non-composite claim, the methods
-     ``get_claim_key(self=None)``, ``get_claim_name(self=None)`` and
-     ``verify(self, value)`` methods must be implemented. for composite claims
-     (that inherit from ``CompositeAttestClaim``), ``verify(self, value)`` is
-     implemented by the base class.
+   * For each claim, the methods ``get_claim_key(self=None)``,
+     ``get_claim_name(self=None)`` must be implemented.
+
    * Other methods of ``AttestationClaim`` are optional to override.
 
+   * Any claim that inherits from ``AttestationClaim`` might have a ``verify``
+     method (``def verify(self, token_item):``). This method is called when the
+     ``verify()`` method of a ``TokenItem`` object is called. ``TokenItem``'s
+     ``verify()`` method walks up the inheritance tree of the ``claim_type``
+     object's class in that ``TokenItem``. If a class in the walk path has a
+     ``verify()`` method, calls it. For further details see ``TokenItem``'s
+     ``_call_verify_with_parents()`` method.
+
+     Any verify method needs to call ``AttestationClaim``'s ``error()`` or
+     ``warning()`` in case of a problem. If the actual class inherits from
+     ``AttestationTokenVerifier`` this can be done like
+     ``self.error('Meaningful error message.')``. In other cases
+     ``self.verifier.error('Meaningful error message.')``
+
 #. Create a file for the new token in `tf-m-tools/iat-verifier/iatverifier`.
 
    * Create a new class for the token type. It must inherit from the class
      ``AttestationTokenVerifier``.
 
    * Implement ``get_claim_key(self=None)`` and ``get_claim_name(self=None)``
-     (The return value of ``get_claim_key(self=None)`` is not used)
 
    * Implement the ``__init__(self, ...)`` function. This function must create a
      list with the claims that are accepted by this token. (Note that the
@@ -280,7 +290,7 @@
 
      The list of claims must be passed to the init function of the base class.
 
-   * Implement ``check_cross_claim_requirements`` for the token if necessary
+     For example see *iat-verifier/iatverifier/cca_token_verifier.py*.
 
 #. Add handling of the new token type to the ``check_iat``, ``decompile_token``,
    and ``compile_token`` scripts.
diff --git a/iat-verifier/iatverifier/attest_token_verifier.py b/iat-verifier/iatverifier/attest_token_verifier.py
index c0f8e5f..3e288b1 100644
--- a/iat-verifier/iatverifier/attest_token_verifier.py
+++ b/iat-verifier/iatverifier/attest_token_verifier.py
@@ -29,6 +29,36 @@
 _CBOR_MAJOR_TYPE_MAP = 5
 _CBOR_MAJOR_TYPE_SEMANTIC_TAG = 6
 
+class TokenItem:
+    """This class represents an item in the token map
+
+    The Field `claim_type` contains an AttestationClaim object, that determines how to interpret the
+    `value` field.
+    The field `value` contains either another TokenItem object or a representation of a claim value
+    (list, dictionary, bytestring...) depending on the value of `claim_type`
+
+    A TokenItem object might have extra fields beyond these as it might be necessary to store
+    properties during parsing, that can aid verifying.
+    """
+    def __init__(self, *, value, claim_type):
+        self.value = value # The value of the claim
+        self.claim_type = claim_type # an AttestationClaim instance
+
+    @classmethod
+    def _call_verify_with_parents(cls, claim_type_class, claim_type, token_item, indent):
+        for parent_class in claim_type_class.__bases__:
+            cls._call_verify_with_parents(parent_class, claim_type, token_item, indent + 2)
+        if "verify" in claim_type_class.__dict__:
+            claim_type_class.verify(claim_type, token_item)
+
+    def verify(self):
+        """Calls claim_type's and its parents' verify method"""
+        claim_type = self.claim_type
+        self.__class__._call_verify_with_parents(claim_type.__class__, claim_type, self, 0)
+
+    def get_token_map(self):
+        return self.claim_type.get_token_map(self)
+
 class AttestationClaim(ABC):
     """
     This class represents a claim.
@@ -38,12 +68,6 @@
 
     This class contains methods that are not abstract. These are here as a
     default behavior, that a derived class might either keep, or override.
-
-    A token is built up as a hierarchy of claim classes. Although it is
-    important, that claim objects don't have a 'value' field. The actual parsed
-    token is stored in a map structure. It is possible to execute operations
-    on a token map, and the operations are defined by claim classes/objects.
-    Such operations are for example verifying a token.
     """
 
     MANDATORY = 0
@@ -54,21 +78,12 @@
         self.config = verifier.config
         self.verifier = verifier
         self.necessity = necessity
-        self.verify_count = 0
-        self.cross_claim_requirement_checker = None
 
     #
     # Abstract methods
     #
 
     @abstractmethod
-    def verify(self, value):
-        """Verify this claim
-
-        Throw an exception if the claim is not valid"""
-        raise NotImplementedError
-
-    @abstractmethod
     def get_claim_key(self=None):
         """Get the key of this claim
 
@@ -92,7 +107,7 @@
         """
         Decode the value of the claim if the value is an UTF-8 string
         """
-        if type(self).is_utf_8():
+        if self.__class__.is_utf_8():
             try:
                 return value.decode()
             except UnicodeDecodeError as exc:
@@ -102,10 +117,6 @@
         else:  # not a UTF-8 value, i.e. a bytestring
             return value
 
-    def claim_found(self):
-        """Return true if verify was called on tis claim instance"""
-        return self.verify_count>0
-
     @classmethod
     def is_utf_8(cls):
         """Returns whether the value of this claim should be UTF-8"""
@@ -121,32 +132,19 @@
         # pylint: disable=unused-argument
         value = token_map
         if parse_raw_value:
-            value = type(self).parse_raw(value)
+            value = self.__class__.parse_raw(value)
         return token_encoder.encode(value)
 
-    def parse_token(self, *, token, verify, check_p_header, lower_case_key):
+    def parse_token(self, *, token, check_p_header, lower_case_key):
         """Parse a token into a map
 
         This function is recursive for composite claims and for token verifiers.
-        A big difference is that the parameter token should be a map for claim
-        objects, and a 'bytes' object for verifiers. The entry point to this
-        function is calling the parse_token function of a verifier.
 
-        From some aspects it would be cleaner to have different functions for
-        this in verifiers and claims, but that would require to do a type check
-        in every recursive step to see which method to call. So instead the
-        method name is the same, and the 'token' parameter is interpreted
-        differently."""
-        # pylint: disable=unused-argument
-        if verify:
-            self.verify(token)
-
-        formatted = type(self).get_formatted_value(token)
-
-        # If the formatted value is still a bytestring then try to decode
-        if isinstance(formatted, bytes):
-            formatted = self.decode(formatted)
-        return formatted
+        The `token` parameter can be interpreted differently in derived classes:
+        - as a raw token that is decoded by the CBOR parsing library
+        - as CBOR encoded token in case of (nested) tokens.
+        """
+        return TokenItem(value=token, claim_type=self)
 
     @classmethod
     def parse_raw(cls, raw_value):
@@ -213,22 +211,23 @@
             msg = 'Invalid {} length: must be at least {} bytes, found {} bytes'
             self.verifier.error(msg.format(name, minimal_length, value_len))
 
+    def get_token_map(self, token_item):
+        formatted = self.__class__.get_formatted_value(token_item.value)
 
-class NonVerifiedClaim(AttestationClaim):
-    """An abstract claim type for which verify() always passes.
+        # If the formatted value is still a bytestring then try to decode
+        if isinstance(formatted, bytes):
+            formatted = self.decode(formatted)
 
-    Can be used for claims for which no verification is implemented."""
-    def verify(self, value):
-        self.verify_count += 1
+        return formatted
 
 class CompositeAttestClaim(AttestationClaim):
     """
     This class represents composite claim.
 
     This class is still abstract, but can contain other claims. This means that
-    a value representing this claim is a dictionary. This claim contains further
-    claims which represent the possible key-value pairs in the value for this
-    claim.
+    a value representing this claim is a dictionary, or a list of dictionaries.
+    This claim contains further claims which represent the possible key-value
+    pairs in the value for this claim.
 
     It is possible that there are requirement that the claims in this claim must
     satisfy, but this can't be checked in the `verify` function of a claim.
@@ -236,15 +235,14 @@
     For example the composite claim can contain a claim type `A`, and a claim
     type `B`, exactly one of the two can be present.
 
-    In this case a method must be passed in the `cross_claim_requirement_checker`
-    parameter of the `__init__` function, that does this check.
+    In this case the class inheriting from this class can have its own verify()
+    method.
     """
 
     def __init__(self,
                  *, verifier,
                  claims,
                  is_list,
-                 cross_claim_requirement_checker,
                  necessity=AttestationClaim.MANDATORY):
         """ Initialise a composite claim.
 
@@ -257,107 +255,128 @@
         super().__init__(verifier=verifier, necessity=necessity)
         self.is_list = is_list
         self.claims = claims
-        self.cross_claim_requirement_checker = cross_claim_requirement_checker
 
     def _get_contained_claims(self):
-        claims = []
         for claim, args in self.claims:
             try:
-                claims.append(claim(**args))
+                yield claim(**args)
             except TypeError as exc:
                 raise TypeError(f"Failed to instantiate '{claim}' with args '{args}' in token " +
                                 f"{type(self.verifier)}\nSee error in exception above.") from exc
-        return claims
 
 
-    def verify(self, value):
-        # No actual verification is done here. The `verify` function of the contained claims
-        # is called during traversing of the token tree.
-        self.verify_count += 1
+    def _verify_dict(self, claim_type, entry_number, dictionary):
+        if not isinstance(dictionary, dict):
+            if self.config.strict:
+                msg = 'The values in token {} must be a dict.'
+                self.verifier.error(msg.format(claim_type.get_claim_name()))
+            else:
+                msg = 'The values in token {} must be a dict, skipping'
+                self.verifier.warning(msg.format(claim_type.get_claim_name()))
+                return
 
-    def _parse_token_dict(self, *, entry_number, token, verify, check_p_header, lower_case_key):
-        ret = {}
-
-        if verify:
-            self.verify(token)
-            if not self._check_type(self.get_claim_name(), token, dict):
-                return None
-        else:
-            if not isinstance(token, dict):
-                return token
-
-        claims = {val.get_claim_key(): val for val in self._get_contained_claims()}
-        for key, val in token.items():
-            if key not in claims.keys():
-                if verify and self.config.strict:
+        claim_names = [val.get_claim_name() for val in claim_type._get_contained_claims()]
+        for claim_name, _ in dictionary.items():
+            if claim_name not in claim_names:
+                if self.config.strict:
                     msg = 'Unexpected {} claim: {}'
-                    self.verifier.error(msg.format(self.get_claim_name(), key))
+                    self.verifier.error(msg.format(claim_type.get_claim_name(), claim_name))
                 else:
                     msg = 'Unexpected {} claim: {}, skipping.'
-                    self.verifier.warning(msg.format(self.get_claim_name(), key))
+                    self.verifier.warning(msg.format(claim_type.get_claim_name(), claim_name))
                     continue
-            try:
-                claim = claims[key]
-                name = claim.get_claim_name()
-                if lower_case_key:
-                    name = name.lower()
-                ret[name] = claim.parse_token(
-                    token=val,
-                    verify=verify,
-                    check_p_header=check_p_header,
-                    lower_case_key=lower_case_key)
-            except Exception:
-                if not self.config.keep_going:
-                    raise
 
-        if verify:
-            self._check_claims_necessity(entry_number, claims)
-            if self.cross_claim_requirement_checker is not None:
-                self.cross_claim_requirement_checker(self.verifier, claims)
+        claims = {val.get_claim_key(): val for val in claim_type._get_contained_claims()}
+        self._check_claims_necessity(entry_number, claims, dictionary)
+        for token_item in dictionary.values():
+            if isinstance(token_item, TokenItem):
+                token_item.verify()
+            else:
+                # the parse of this token item failed. So it cannot be verified.
+                # Warning had been reported during parsing.
+                pass
 
-        return ret
+    def verify(self, token_item):
+        if self.is_list:
+            if not isinstance(token_item.value, list):
+                if self.config.strict:
+                    msg = 'The value of this token {} must be a list.'
+                    self.verifier.error(msg.format(self.get_claim_name()))
+                else:
+                    msg = 'The value of this token {} must be a list, skipping'
+                    self.verifier.warning(msg.format(self.get_claim_name()))
+                    return
+            for entry_number, list_item in enumerate(token_item.value):
+                self._verify_dict(token_item.claim_type, entry_number, list_item)
+        else:
+            self._verify_dict(token_item.claim_type, None, token_item.value)
 
-    def _check_claims_necessity(self, entry_number, claims):
-        for claim in claims.values():
-            if not claim.claim_found():
-                if claim.necessity==AttestationClaim.MANDATORY:
-                    msg = (f'Invalid IAT: missing MANDATORY claim "{claim.get_claim_name()}" '
-                        f'from {self.get_claim_name()}')
-                    if entry_number is not None:
-                        msg += f' at index {entry_number}'
-                    self.verifier.error(msg)
-                elif claim.necessity==AttestationClaim.RECOMMENDED:
-                    msg = (f'Missing RECOMMENDED claim "{claim.get_claim_name()}" '
-                        f'from {self.get_claim_name()}')
-                    if entry_number is not None:
-                        msg += f' at index {entry_number}'
-                    self.verifier.warning(msg)
+    def _parse_token_dict(self, *, entry_number, token, check_p_header, lower_case_key):
+        claim_value = {}
 
-    def parse_token(self, *, token, verify, check_p_header, lower_case_key):
+        if not isinstance(token, dict):
+            claim_value = token
+        else:
+            claims = {val.get_claim_key(): val for val in self._get_contained_claims()}
+            for key, val in token.items():
+                try:
+                    claim = claims[key]
+                    name = claim.get_claim_name()
+                    if lower_case_key:
+                        name = name.lower()
+                    claim_value[name] = claim.parse_token(
+                        token=val,
+                        check_p_header=check_p_header,
+                        lower_case_key=lower_case_key)
+                except KeyError:
+                    claim_value[key] = val
+                except Exception:
+                    if not self.config.keep_going:
+                        raise
+        return claim_value
+
+    def _check_claims_necessity(self, entry_number, claims, dictionary):
+        mandatory_claim_names = [claim.get_claim_name() for claim in claims.values() if claim.necessity == AttestationClaim.MANDATORY]
+        recommended_claim_names = [claim.get_claim_name() for claim in claims.values() if claim.necessity == AttestationClaim.RECOMMENDED]
+        dictionary_claim_names = dictionary.keys()
+
+        for mandatory_claim_name in mandatory_claim_names:
+            if mandatory_claim_name not in dictionary_claim_names:
+                msg = (f'Invalid IAT: missing MANDATORY claim "{mandatory_claim_name}" '
+                    f'from {self.get_claim_name()}')
+                if entry_number is not None:
+                    msg += f' at index {entry_number}'
+                self.verifier.error(msg)
+
+        for recommended_claim_name in recommended_claim_names:
+            if recommended_claim_name not in dictionary_claim_names:
+                msg = (f'Missing RECOMMENDED claim "{recommended_claim_name}" '
+                    f'from {self.get_claim_name()}')
+                if entry_number is not None:
+                    msg += f' at index {entry_number}'
+                self.verifier.warning(msg)
+
+    def parse_token(self, *, token, check_p_header, lower_case_key):
         """This expects a raw token map as 'token'"""
 
         if self.is_list:
-            ret = []
-            if verify:
-                if not self._check_type(self.get_claim_name(), token, list):
-                    return None
+            claim_value = []
+            if not isinstance(token, list):
+                claim_value = token
             else:
-                if not isinstance(token, list):
-                    return token
-            for entry_number, entry in enumerate(token):
-                ret.append(self._parse_token_dict(
-                    entry_number=entry_number,
-                    check_p_header=check_p_header,
-                    token=entry,
-                    verify=verify,
-                    lower_case_key=lower_case_key))
-            return ret
-        return self._parse_token_dict(
-            entry_number=None,
-            check_p_header=check_p_header,
-            token=token,
-            verify=verify,
-            lower_case_key=lower_case_key)
+                for entry_number, entry in enumerate(token):
+                    claim_value.append(self._parse_token_dict(
+                            entry_number=entry_number,
+                            check_p_header=check_p_header,
+                            token=entry,
+                            lower_case_key=lower_case_key))
+        else:
+            claim_value = self._parse_token_dict(
+                entry_number=None,
+                check_p_header=check_p_header,
+                token=token,
+                lower_case_key=lower_case_key)
+        return TokenItem(value=claim_value, claim_type=self)
 
 
     def _encode_dict(self, token_encoder, token_map, *, add_p_header, name_as_key, parse_raw_value):
@@ -410,6 +429,20 @@
                 name_as_key=name_as_key,
                 parse_raw_value=parse_raw_value)
 
+    def get_token_map(self, token_item):
+        if self.is_list:
+            ret = []
+            for token_item_dict in token_item.value:
+                token_dict = {}
+                for key, claim_token_item in token_item_dict.items():
+                    token_dict[key] = claim_token_item.get_token_map()
+                ret.append(token_dict)
+            return ret
+        else:
+            token_dict = {}
+            for key, claim_token_item in token_item.value.items():
+                token_dict[key] = claim_token_item.get_token_map()
+            return token_dict
 
 @dataclass
 class VerifierConfiguration:
@@ -430,13 +463,9 @@
         return None
 
     def get_claim_name(self=None):
-        return "TOKEN_CLAIM"
+        return "TOKEN_ROOT_CLAIMS"
 
-# This class inherits from NonVerifiedClaim. The actual claims in the token are
-# checked by the AttestTokenRootClaims object owned by this verifier. The
-# verify() function of the AttestTokenRootClaims object is called during
-# traversing the claim tree.
-class AttestationTokenVerifier(NonVerifiedClaim):
+class AttestationTokenVerifier(AttestationClaim):
     """Abstract base class for attestation token verifiers"""
 
     SIGN_METHOD_SIGN1 = "sign"
@@ -472,11 +501,6 @@
     def _parse_p_header(self, msg):
         """Throw exception in case of error"""
 
-    @staticmethod
-    @abstractmethod
-    def check_cross_claim_requirements(verifier, claims):
-        """Throw exception in case of error"""
-
     def _get_cose_alg(self):
         return self.cose_alg
 
@@ -503,7 +527,6 @@
                 verifier=self,
                 claims=claims,
                 is_list=False,
-                cross_claim_requirement_checker=type(self).check_cross_claim_requirements,
                 necessity=necessity)
 
         super().__init__(verifier=self, necessity=necessity)
@@ -594,7 +617,6 @@
         err_msg = f'Unexpected method "{self._get_method()}"; must be one of: sign, mac'
         raise ValueError(err_msg)
 
-
     def convert_map_to_token(
             self,
             token_encoder,
@@ -635,7 +657,7 @@
             else:
                 token_encoder.encode_bytestring(signed_token)
 
-    def parse_token(self, *, token, verify, check_p_header, lower_case_key):
+    def parse_token(self, *, token, check_p_header, lower_case_key):
         if self._get_method() == AttestationTokenVerifier.SIGN_METHOD_RAW:
             payload = token
         else:
@@ -643,7 +665,8 @@
                 payload = self._get_cose_payload(
                     token,
                     check_p_header=check_p_header,
-                    verify_signature=(verify and self._get_signing_key() is not None))
+                    # signature verification is done in the verify function
+                    verify_signature=False)
             except Exception as exc:
                 msg = f'Bad COSE: {exc}'
                 self.error(msg)
@@ -654,30 +677,52 @@
             msg = f'Invalid CBOR: {exc}'
             self.error(msg)
 
-        wrapping_tag = self._get_wrapping_tag()
-
         if isinstance(raw_map, _cbor2.CBORTag):
+            raw_map_tag = raw_map.tag
+            raw_map = raw_map.value
+        else:
+            raw_map_tag = None
+
+        token_items = self.claims.parse_token(
+            token=raw_map,
+            check_p_header=check_p_header,
+            lower_case_key=lower_case_key)
+
+        ret = TokenItem(value=token_items, claim_type=self)
+        ret.wrapping_tag = raw_map_tag
+        ret.token = token
+        ret.check_p_header = check_p_header
+        return ret
+
+    def verify(self, token_item):
+        if self._get_method() != AttestationTokenVerifier.SIGN_METHOD_RAW:
+            try:
+                self._get_cose_payload(
+                    token_item.token,
+                    check_p_header=token_item.check_p_header,
+                    verify_signature=(self._get_signing_key() is not None))
+            except Exception as exc:
+                msg = f'Bad COSE: {exc}'
+                raise ValueError(msg) from exc
+
+        wrapping_tag = self._get_wrapping_tag()
+        if token_item.wrapping_tag is not None:
             if wrapping_tag is None:
-                msg = f'Invalid token: Unexpected tag (0x{raw_map.tag:x}) in token {self.get_claim_name()}'
+                msg = f'Invalid token: Unexpected tag (0x{token_item.wrapping_tag:x}) in token {self.get_claim_name()}'
                 self.error(msg)
             else:
-                if wrapping_tag != raw_map.tag:
-                    msg = f'Invalid token: token {self.get_claim_name()} is wrapped in tag 0x{raw_map.tag:x} instead of 0x{wrapping_tag:x}'
+                if wrapping_tag != token_item.wrapping_tag:
+                    msg = f'Invalid token: token {self.get_claim_name()} is wrapped in tag 0x{token_item.wrapping_tag:x} instead of 0x{wrapping_tag:x}'
                     self.error(msg)
-            raw_map = raw_map.value
         else:
             if wrapping_tag is not None:
                 msg = f'Invalid token: token {self.get_claim_name()} should be wrapped in tag 0x{wrapping_tag:x}'
                 self.error(msg)
 
-        if verify:
-            self.verify(token)
+        token_item.value.verify()
 
-        return self.claims.parse_token(
-            token=raw_map,
-            check_p_header=check_p_header,
-            verify=verify,
-            lower_case_key=lower_case_key)
+    def get_token_map(self, token_item):
+        return self.claims.get_token_map(token_item.value)
 
     def error(self, message, *, exception=None):
         """Act on an error depending on the configuration of this verifier"""
diff --git a/iat-verifier/iatverifier/cca_claims.py b/iat-verifier/iatverifier/cca_claims.py
index 684ece0..1b1ccbe 100644
--- a/iat-verifier/iatverifier/cca_claims.py
+++ b/iat-verifier/iatverifier/cca_claims.py
@@ -8,7 +8,7 @@
 from collections.abc import Iterable
 import logging
 
-from iatverifier.attest_token_verifier import AttestationClaim, NonVerifiedClaim, CompositeAttestClaim
+from iatverifier.attest_token_verifier import AttestationClaim, CompositeAttestClaim
 
 logger = logging.getLogger('iat-verifiers')
 
@@ -23,16 +23,15 @@
     def get_claim_name(self=None):
         return 'CCA_REALM_CHALLENGE'
 
-    def verify(self, value):
-        self._validate_bytestring_length_equals(value, self.get_claim_name(), 64)
+    def verify(self, token_item):
+        self._validate_bytestring_length_equals(token_item.value, self.get_claim_name(), 64)
         if self.expected_challenge_byte is not None:
-            for i, b in enumerate(value):
+            for i, b in enumerate(token_item.value):
                 if b != self.expected_challenge_byte:
-                    print (f'Challenge = {value}')
+                    print (f'Challenge = {token_item.value}')
                     msg = 'Invalid CHALLENGE byte at {:d}: 0x{:02x} instead of 0x{:02x}'
                     self.verifier.error(msg.format(i, b, self.expected_challenge_byte))
                     break
-        self.verify_count += 1
 
 class CCARealmPersonalizationValue(AttestationClaim):
     def get_claim_key(self=None):
@@ -41,9 +40,8 @@
     def get_claim_name(self=None):
         return 'CCA_REALM_PERSONALIZATION_VALUE'
 
-    def verify(self, value):
-        self._validate_bytestring_length_equals(value, self.get_claim_name(), 64)
-        self.verify_count += 1
+    def verify(self, token_item):
+        self._validate_bytestring_length_equals(token_item.value, self.get_claim_name(), 64)
 
 class CCARealmInitialMeasurementClaim(AttestationClaim):
     def get_claim_key(self=None):
@@ -52,9 +50,8 @@
     def get_claim_name(self=None):
         return 'CCA_REALM_INITIAL_MEASUREMENT'
 
-    def verify(self, value):
-        self._check_type(self.get_claim_name(), value, bytes)
-        self.verify_count += 1
+    def verify(self, token_item):
+        self._check_type(self.get_claim_name(), token_item.value, bytes)
 
 class CCARealmExtensibleMeasurementsClaim(AttestationClaim):
     def get_claim_key(self=None):
@@ -63,21 +60,20 @@
     def get_claim_name(self=None):
         return 'CCA_REALM_EXTENSIBLE_MEASUREMENTS'
 
-    def verify(self, value):
+    def verify(self, token_item):
         min_measurement_count = 4
         max_measurement_count = 4
-        if not isinstance(value, Iterable):
+        if not isinstance(token_item.value, Iterable):
             msg = 'Invalid {:s}: Value must be a list.'
             self.verifier.error(msg.format(self.get_claim_name()))
-        if len(value) < min_measurement_count or len(value) > max_measurement_count:
+        if len(token_item.value) < min_measurement_count or len(token_item.value) > max_measurement_count:
             msg = 'Invalid {:s}: Value must be a list of min {:d} elements and max {:d} elements.'
             self.verifier.error(msg.format(self.get_claim_name(), min_measurement_count,
                 max_measurement_count))
-        for v in value:
+        for v in token_item.value:
             self._validate_bytestring_length_one_of(v, self.get_claim_name()+f'[{str()}]', [32, 64])
-        self.verify_count += 1
 
-class CCARealmHashAlgorithmIdClaim(NonVerifiedClaim):
+class CCARealmHashAlgorithmIdClaim(AttestationClaim):
     def get_claim_key(self=None):
         return 44236
 
@@ -88,7 +84,7 @@
     def is_utf_8(cls):
         return True
 
-class CCARealmPubKeyHashAlgorithmIdClaim(NonVerifiedClaim):
+class CCARealmPubKeyHashAlgorithmIdClaim(AttestationClaim):
     def get_claim_key(self=None):
         return 44240
 
@@ -106,9 +102,8 @@
     def get_claim_name(self=None):
         return 'CCA_REALM_PUB_KEY'
 
-    def verify(self, value):
-        self._validate_bytestring_length_equals(value, self.get_claim_name(), 97)
-        self.verify_count += 1
+    def verify(self, token_item):
+        self._validate_bytestring_length_equals(token_item.value, self.get_claim_name(), 97)
 
 class CCAAttestationProfileClaim(AttestationClaim):
     def get_claim_key(self=None):
@@ -121,13 +116,12 @@
     def is_utf_8(cls):
         return True
 
-    def verify(self, value):
+    def verify(self, token_item):
         expected_value = "http://arm.com/CCA-SSD/1.0.0"
-        self._check_type(self.get_claim_name(), value, str)
-        if value != expected_value:
+        self._check_type(self.get_claim_name(), token_item.value, str)
+        if token_item.value != expected_value:
             msg = 'Invalid Attest profile "{}": must be "{}"'
-            self.verifier.error(msg.format(value, expected_value))
-        self.verify_count += 1
+            self.verifier.error(msg.format(token_item.value, expected_value))
 
 class CCAPlatformChallengeClaim(AttestationClaim):
     def get_claim_key(self=None):
@@ -136,9 +130,8 @@
     def get_claim_name(self=None):
         return 'CCA_PLATFORM_CHALLENGE'
 
-    def verify(self, value):
-        self._validate_bytestring_length_one_of(value, self.get_claim_name(), [32, 48, 64])
-        self.verify_count += 1
+    def verify(self, token_item):
+        self._validate_bytestring_length_one_of(token_item.value, self.get_claim_name(), [32, 48, 64])
 
 class CCAPlatformImplementationIdClaim(AttestationClaim):
     def get_claim_key(self=None):
@@ -147,9 +140,8 @@
     def get_claim_name(self=None):
         return 'CCA_PLATFORM_IMPLEMENTATION_ID'
 
-    def verify(self, value):
-        self._validate_bytestring_length_equals(value, self.get_claim_name(), 32)
-        self.verify_count += 1
+    def verify(self, token_item):
+        self._validate_bytestring_length_equals(token_item.value, self.get_claim_name(), 32)
 
 class CCAPlatformInstanceIdClaim(AttestationClaim):
     def get_claim_key(self=None):
@@ -158,12 +150,11 @@
     def get_claim_name(self=None):
         return 'CCA_PLATFORM_INSTANCE_ID'
 
-    def verify(self, value):
-        self._validate_bytestring_length_between(value, self.get_claim_name(), 7, 33)
-        if value[0] != 0x01:
+    def verify(self, token_item):
+        self._validate_bytestring_length_between(token_item.value, self.get_claim_name(), 7, 33)
+        if token_item.value[0] != 0x01:
             msg = 'Invalid Instance ID first byte "0x{:02x}": must be "0x01"'
-            self.verifier.error(msg.format(value[0]))
-        self.verify_count += 1
+            self.verifier.error(msg.format(token_item.value[0]))
 
 class CCAPlatformConfigClaim(AttestationClaim):
     def get_claim_key(self=None):
@@ -172,9 +163,8 @@
     def get_claim_name(self=None):
         return 'CCA_PLATFORM_CONFIG'
 
-    def verify(self, value):
-        self._check_type(self.get_claim_name(), value, bytes)
-        self.verify_count += 1
+    def verify(self, token_item):
+        self._check_type(self.get_claim_name(), token_item.value, bytes)
 
 class CCAPlatformLifecycleClaim(AttestationClaim):
 
@@ -222,19 +212,18 @@
                 return f"{text}_{value:04x}".lower()
         return f"INVALID_{value:04x}"
 
-    def verify(self, value):
-        self._check_type(self.get_claim_name, value, int)
+    def verify(self, token_item):
+        self._check_type(self.get_claim_name, token_item.value, int)
         value_valid = False
         for _, min, max in CCAPlatformLifecycleClaim.SL_VALUES:
-            if min <= value <= max:
+            if min <= token_item.value <= max:
                 value_valid = True
                 break
         if not value_valid:
             msg = 'Invalid Platform Lifecycle claim "0x{:02x}"'
-            self.verifier.error(msg.format(value))
-        self.verify_count += 1
+            self.verifier.error(msg.format(token_item.value))
 
-class CCASwCompHashAlgIdClaim(NonVerifiedClaim):
+class CCASwCompHashAlgIdClaim(AttestationClaim):
     def get_claim_key(self=None):
         return 6
 
@@ -253,7 +242,7 @@
     def get_claim_name(self=None):
         return 'CCA_PLATFORM_SW_COMPONENTS'
 
-class CCAPlatformVerificationServiceClaim(NonVerifiedClaim):
+class CCAPlatformVerificationServiceClaim(AttestationClaim):
     def get_claim_key(self=None):
         return 2400
 
@@ -264,7 +253,7 @@
     def is_utf_8(cls):
         return True
 
-class CCAPlatformHashAlgorithmIdClaim(NonVerifiedClaim):
+class CCAPlatformHashAlgorithmIdClaim(AttestationClaim):
     def get_claim_key(self=None):
         return 2402
 
diff --git a/iat-verifier/iatverifier/cca_token_verifier.py b/iat-verifier/iatverifier/cca_token_verifier.py
index dad5680..79fee2c 100644
--- a/iat-verifier/iatverifier/cca_token_verifier.py
+++ b/iat-verifier/iatverifier/cca_token_verifier.py
@@ -66,11 +66,6 @@
             cose_alg=Verifier.COSE_ALG_ES256,
             signing_key=None)
 
-    @staticmethod
-    def check_cross_claim_requirements(verifier, claims):
-        pass
-
-
 class CCARealmTokenVerifier(Verifier):
     def get_claim_key(self=None):
         return 44241
@@ -113,11 +108,6 @@
             cose_alg=cose_alg,
             signing_key=signing_key)
 
-    @staticmethod
-    def check_cross_claim_requirements(verifier, claims):
-        pass
-
-
 class CCAPlatformTokenVerifier(Verifier):
     def get_claim_key(self=None):
         return 44234 #0xACCA
@@ -159,7 +149,7 @@
             (CCAPlatformInstanceIdClaim, {'verifier':self, 'necessity': Claim.MANDATORY}),
             (CCAPlatformConfigClaim, {'verifier':self, 'necessity': Claim.MANDATORY}),
             (CCAPlatformLifecycleClaim, {'verifier':self, 'necessity': Claim.MANDATORY}),
-            (CCAPlatformSwComponentsClaim, {'verifier':self, 'claims': sw_component_claims, 'is_list': True, 'cross_claim_requirement_checker':None, 'necessity': Claim.MANDATORY}),
+            (CCAPlatformSwComponentsClaim, {'verifier':self, 'claims': sw_component_claims, 'is_list': True, 'necessity': Claim.MANDATORY}),
             (CCAPlatformVerificationServiceClaim, {'verifier':self, 'necessity': Claim.OPTIONAL}),
             (CCAPlatformHashAlgorithmIdClaim, {'verifier':self, 'necessity': Claim.MANDATORY}),
         ]
@@ -172,8 +162,3 @@
             method=method,
             cose_alg=cose_alg,
             signing_key=signing_key)
-
-    @staticmethod
-    def check_cross_claim_requirements(verifier, claims):
-        pass
-
diff --git a/iat-verifier/iatverifier/psa_2_0_0_token_claims.py b/iat-verifier/iatverifier/psa_2_0_0_token_claims.py
index 63d49ee..06f0e78 100644
--- a/iat-verifier/iatverifier/psa_2_0_0_token_claims.py
+++ b/iat-verifier/iatverifier/psa_2_0_0_token_claims.py
@@ -7,7 +7,7 @@
 
 import string
 
-from iatverifier.attest_token_verifier import AttestationClaim, NonVerifiedClaim
+from iatverifier.attest_token_verifier import AttestationClaim
 from iatverifier.attest_token_verifier import CompositeAttestClaim
 
 # IAT custom claims
@@ -28,12 +28,11 @@
     def get_claim_name(self=None):
         return 'INSTANCE_ID'
 
-    def verify(self, value):
-        self._validate_bytestring_length_equals(value, 'INSTANCE_ID', self.expected_len)
-        if value[0] != 0x01:
+    def verify(self, token_item):
+        self._validate_bytestring_length_equals(token_item.value, 'INSTANCE_ID', self.expected_len)
+        if token_item.value[0] != 0x01:
             msg = 'Invalid INSTANCE_ID: first byte must be 0x01, found: 0x{}'
-            self.verifier.error(msg.format(value[0]))
-        self.verify_count += 1
+            self.verifier.error(msg.format(token_item.value[0]))
 
 
 class ChallengeClaim(AttestationClaim):
@@ -46,17 +45,16 @@
     def get_claim_name(self=None):
         return 'CHALLENGE'
 
-    def verify(self, value):
-        self._check_type('CHALLENGE', value, bytes)
+    def verify(self, token_item):
+        self._check_type('CHALLENGE', token_item.value, bytes)
 
-        value_len = len(value)
+        value_len = len(token_item.value)
         if value_len not in ChallengeClaim.HASH_SIZES:
             msg = 'Invalid CHALLENGE length; must one of {}, found {} bytes'
             self.verifier.error(msg.format(ChallengeClaim.HASH_SIZES, value_len))
-        self.verify_count += 1
 
 
-class ImplementationIdClaim(NonVerifiedClaim):
+class ImplementationIdClaim(AttestationClaim):
     """Class representing a PSA Attestation Token Implementation ID claim"""
     def get_claim_key(self=None):
         return ARM_RANGE + 3
@@ -67,20 +65,19 @@
 
 class CertificationReference(AttestationClaim):
     """Class representing a PSA Attestation Token Certification Reference claim"""
-    def verify(self, value):
-        self._check_type('CERTIFICATION_REFERENCE', value, str)
+    def verify(self, token_item):
+        self._check_type('CERTIFICATION_REFERENCE', token_item.value, str)
 
-        value_len = len(value)
+        value_len = len(token_item.value)
         expected_len = 19 # 'EAN13-Version' 13 + '-' + 5. e.g.:0604565272829-10010
-        if len(value) != expected_len:
+        if len(token_item.value) != expected_len:
             msg = 'Invalid CERTIFICATION_REFERENCE length; must be {} characters, found {} characters'
             self.verifier.error(msg.format(expected_len, value_len))
-        for idx, character in enumerate(value):
+        for idx, character in enumerate(token_item.value):
             if character not in string.digits and character not in '-':
                 msg = 'Invalid character {} at position {}'
                 self.verifier.error(msg.format(character, idx+1))
 
-        self.verify_count += 1
 
     def get_claim_key(self=None):
         return ARM_RANGE + 5
@@ -101,7 +98,7 @@
     def get_claim_name(self=None):
         return 'SW_COMPONENTS'
 
-class SWComponentTypeClaim(NonVerifiedClaim):
+class SWComponentTypeClaim(AttestationClaim):
     """Class representing a PSA Attestation Token Software Component Measurement Type claim"""
     def get_claim_key(self=None):
         return SW_COMPONENT_RANGE + 1
@@ -121,9 +118,8 @@
     def get_claim_name(self=None):
         return 'CLIENT_ID'
 
-    def verify(self, value):
-        self._check_type('CLIENT_ID', value, int)
-        self.verify_count += 1
+    def verify(self, token_item):
+        self._check_type('CLIENT_ID', token_item.value, int)
 
 class SecurityLifecycleClaim(AttestationClaim):
     """Class representing a PSA Attestation Token Security Lifecycle claim"""
@@ -152,9 +148,8 @@
     def get_claim_name(self=None):
         return 'SECURITY_LIFECYCLE'
 
-    def verify(self, value):
-        self._check_type('SECURITY_LIFECYCLE', value, int)
-        self.verify_count += 1
+    def verify(self, token_item):
+        self._check_type('SECURITY_LIFECYCLE', token_item.value, int)
 
     @staticmethod
     def parse_raw(raw_value):
@@ -174,13 +169,12 @@
     def get_claim_name(self=None):
         return 'PROFILE_ID'
 
-    def verify(self, value):
+    def verify(self, token_item):
         expected_value = "http://arm.com/psa/2.0.0"
-        self._check_type(self.get_claim_name(), value, str)
-        if value != expected_value:
+        self._check_type(self.get_claim_name(), token_item.value, str)
+        if token_item.value != expected_value:
             msg = 'Invalid Attest profile "{}": must be "{}"'
-            self.verifier.error(msg.format(value, expected_value))
-        self.verify_count += 1
+            self.verifier.error(msg.format(token_item.value, expected_value))
 
     @classmethod
     def is_utf_8(cls):
@@ -195,12 +189,11 @@
     def get_claim_name(self=None):
         return 'BOOT_SEED'
 
-    def verify(self, value):
-        self._validate_bytestring_length_is_at_least(value, 'BOOT_SEED', 32)
-        self.verify_count += 1
+    def verify(self, token_item):
+        self._validate_bytestring_length_is_at_least(token_item.value, 'BOOT_SEED', 32)
 
 
-class VerificationServiceClaim(NonVerifiedClaim):
+class VerificationServiceClaim(AttestationClaim):
     """Class representing a PSA Attestation Token Verification Service Indicator claim"""
     def get_claim_key(self=None):
         return ARM_RANGE + 7 # originator
@@ -221,12 +214,11 @@
     def get_claim_name(self=None):
         return 'SIGNER_ID'
 
-    def verify(self, value):
-        self._validate_bytestring_length_is_at_least(value, 'SIGNER_ID', 32)
-        self.verify_count += 1
+    def verify(self, token_item):
+        self._validate_bytestring_length_is_at_least(token_item.value, 'SIGNER_ID', 32)
 
 
-class SwComponentVersionClaim(NonVerifiedClaim):
+class SwComponentVersionClaim(AttestationClaim):
     """Class representing a PSA Attestation Token Software Component Version claim"""
     def get_claim_key(self=None):
         return SW_COMPONENT_RANGE + 4
@@ -247,12 +239,11 @@
     def get_claim_name(self=None):
         return 'MEASUREMENT_VALUE'
 
-    def verify(self, value):
-        self._validate_bytestring_length_is_at_least(value, 'MEASUREMENT', 32)
-        self.verify_count += 1
+    def verify(self, token_item):
+        self._validate_bytestring_length_is_at_least(token_item.value, 'MEASUREMENT', 32)
 
 
-class MeasurementDescriptionClaim(NonVerifiedClaim):
+class MeasurementDescriptionClaim(AttestationClaim):
     """Class representing a PSA Attestation Token Software Component Measurement description claim"""
     def get_claim_key(self=None):
         return SW_COMPONENT_RANGE + 6
diff --git a/iat-verifier/iatverifier/psa_2_0_0_token_verifier.py b/iat-verifier/iatverifier/psa_2_0_0_token_verifier.py
index b3ff4f1..e8ae28a 100644
--- a/iat-verifier/iatverifier/psa_2_0_0_token_verifier.py
+++ b/iat-verifier/iatverifier/psa_2_0_0_token_verifier.py
@@ -56,7 +56,7 @@
             (ImplementationIdClaim, {'verifier': self, 'necessity': Claim.MANDATORY}),
             (BootSeedClaim, {'verifier': self, 'necessity': Claim.MANDATORY}),
             (CertificationReference, {'verifier': self, 'necessity': Claim.OPTIONAL}),
-            (SWComponentsClaim, {'verifier': self, 'claims': sw_component_claims, 'is_list': True, 'cross_claim_requirement_checker':None, 'necessity': Claim.MANDATORY}),
+            (SWComponentsClaim, {'verifier': self, 'claims': sw_component_claims, 'is_list': True, 'necessity': Claim.MANDATORY}),
             (ChallengeClaim, {'verifier': self, 'necessity': Claim.MANDATORY}),
             (InstanceIdClaim, {'verifier': self, 'expected_len': 33, 'necessity': Claim.MANDATORY}),
             (VerificationServiceClaim, {'verifier': self, 'necessity': Claim.OPTIONAL}),
@@ -74,7 +74,3 @@
         # Set the properties of this token:
         self.signing_key_set = False # None is a valid value for signing_key
         self.signing_key = signing_key
-
-    @staticmethod
-    def check_cross_claim_requirements(verifier, claims):
-        pass
diff --git a/iat-verifier/iatverifier/psa_iot_profile1_token_claims.py b/iat-verifier/iatverifier/psa_iot_profile1_token_claims.py
index 44ed4db..a51b544 100644
--- a/iat-verifier/iatverifier/psa_iot_profile1_token_claims.py
+++ b/iat-verifier/iatverifier/psa_iot_profile1_token_claims.py
@@ -11,7 +11,7 @@
 
 import string
 
-from iatverifier.attest_token_verifier import AttestationClaim, NonVerifiedClaim
+from iatverifier.attest_token_verifier import AttestationClaim
 from iatverifier.attest_token_verifier import CompositeAttestClaim
 
 # IAT custom claims
@@ -32,12 +32,11 @@
     def get_claim_name(self=None):
         return 'INSTANCE_ID'
 
-    def verify(self, value):
-        self._validate_bytestring_length_equals(value, 'INSTANCE_ID', self.expected_len)
-        if value[0] != 0x01:
+    def verify(self, token_item):
+        self._validate_bytestring_length_equals(token_item.value, 'INSTANCE_ID', self.expected_len)
+        if token_item.value[0] != 0x01:
             msg = 'Invalid INSTANCE_ID: first byte must be 0x01, found: 0x{}'
-            self.verifier.error(msg.format(value[0]))
-        self.verify_count += 1
+            self.verifier.error(msg.format(token_item.value[0]))
 
 
 class ChallengeClaim(AttestationClaim):
@@ -50,17 +49,16 @@
     def get_claim_name(self=None):
         return 'CHALLENGE'
 
-    def verify(self, value):
-        self._check_type('CHALLENGE', value, bytes)
+    def verify(self, token_item):
+        self._check_type('CHALLENGE', token_item.value, bytes)
 
-        value_len = len(value)
+        value_len = len(token_item.value)
         if value_len not in ChallengeClaim.HASH_SIZES:
             msg = 'Invalid CHALLENGE length; must one of {}, found {} bytes'
             self.verifier.error(msg.format(ChallengeClaim.HASH_SIZES, value_len))
-        self.verify_count += 1
 
 
-class ImplementationIdClaim(NonVerifiedClaim):
+class ImplementationIdClaim(AttestationClaim):
     """Class representing a PSA Attestation Token Implementation ID claim"""
     def get_claim_key(self=None):
         return ARM_RANGE - 3
@@ -71,21 +69,20 @@
 
 class HardwareVersionClaim(AttestationClaim):
     """Class representing a PSA Attestation Token Hardware version claim"""
-    def verify(self, value):
-        self._check_type('HARDWARE_VERSION', value, str)
+    def verify(self, token_item):
+        self._check_type('HARDWARE_VERSION', token_item.value, str)
 
-        value_len = len(value)
+        value_len = len(token_item.value)
         expected_len = 19 # 'EAN13-Version' 13 + 1 + 5. e.g.:0604565272829-10010
-        if len(value) != expected_len:
+        if len(token_item.value) != expected_len:
             msg = 'Invalid HARDWARE_VERSION length; must be {} characters, found {} characters'
             self.verifier.error(msg.format(expected_len, value_len))
-        for idx, character in enumerate(value):
+        for idx, character in enumerate(token_item.value):
             if character not in string.digits:
                 if idx != 13 or character not in '-':
                     msg = 'Invalid character {} at position {}'
                     self.verifier.error(msg.format(character, idx+1))
 
-        self.verify_count += 1
 
     def get_claim_key(self=None):
         return ARM_RANGE - 5
@@ -106,7 +103,7 @@
     def get_claim_name(self=None):
         return 'SW_COMPONENTS'
 
-class SWComponentTypeClaim(NonVerifiedClaim):
+class SWComponentTypeClaim(AttestationClaim):
     """Class representing a PSA Attestation Token Software Component Measurement Type claim"""
     def get_claim_key(self=None):
         return SW_COMPONENT_RANGE + 1
@@ -119,7 +116,7 @@
         return True
 
 
-class NoMeasurementsClaim(NonVerifiedClaim):
+class NoMeasurementsClaim(AttestationClaim):
     """Class representing a PSA Attestation Token No Software Measurements claim"""
     def get_claim_key(self=None):
         return ARM_RANGE - 7
@@ -136,9 +133,8 @@
     def get_claim_name(self=None):
         return 'CLIENT_ID'
 
-    def verify(self, value):
-        self._check_type('CLIENT_ID', value, int)
-        self.verify_count += 1
+    def verify(self, token_item):
+        self._check_type('CLIENT_ID', token_item.value, int)
 
 class SecurityLifecycleClaim(AttestationClaim):
     """Class representing a PSA Attestation Token Security Lifecycle claim"""
@@ -167,9 +163,8 @@
     def get_claim_name(self=None):
         return 'SECURITY_LIFECYCLE'
 
-    def verify(self, value):
-        self._check_type('SECURITY_LIFECYCLE', value, int)
-        self.verify_count += 1
+    def verify(self, token_item):
+        self._check_type('SECURITY_LIFECYCLE', token_item.value, int)
 
     @classmethod
     def parse_raw(cls, raw_value):
@@ -189,9 +184,8 @@
     def get_claim_name(self=None):
         return 'PROFILE_ID'
 
-    def verify(self, value):
-        self._check_type('PROFILE_ID', value, str)
-        self.verify_count += 1
+    def verify(self, token_item):
+        self._check_type('PROFILE_ID', token_item.value, str)
 
     @classmethod
     def is_utf_8(cls):
@@ -206,12 +200,11 @@
     def get_claim_name(self=None):
         return 'BOOT_SEED'
 
-    def verify(self, value):
-        self._validate_bytestring_length_is_at_least(value, 'BOOT_SEED', 32)
-        self.verify_count += 1
+    def verify(self, token_item):
+        self._validate_bytestring_length_is_at_least(token_item.value, 'BOOT_SEED', 32)
 
 
-class VerificationServiceClaim(NonVerifiedClaim):
+class VerificationServiceClaim(AttestationClaim):
     """Class representing a PSA Attestation Token Verification Service Indicator claim"""
     def get_claim_key(self=None):
         return ARM_RANGE - 10 # originator
@@ -232,12 +225,11 @@
     def get_claim_name(self=None):
         return 'SIGNER_ID'
 
-    def verify(self, value):
-        self._validate_bytestring_length_is_at_least(value, 'SIGNER_ID', 32)
-        self.verify_count += 1
+    def verify(self, token_item):
+        self._validate_bytestring_length_is_at_least(token_item.value, 'SIGNER_ID', 32)
 
 
-class SwComponentVersionClaim(NonVerifiedClaim):
+class SwComponentVersionClaim(AttestationClaim):
     """Class representing a PSA Attestation Token Software Component Version claim"""
     def get_claim_key(self=None):
         return SW_COMPONENT_RANGE + 4
@@ -258,12 +250,11 @@
     def get_claim_name(self=None):
         return 'MEASUREMENT_VALUE'
 
-    def verify(self, value):
-        self._validate_bytestring_length_is_at_least(value, 'MEASUREMENT', 32)
-        self.verify_count += 1
+    def verify(self, token_item):
+        self._validate_bytestring_length_is_at_least(token_item.value, 'MEASUREMENT', 32)
 
 
-class MeasurementDescriptionClaim(NonVerifiedClaim):
+class MeasurementDescriptionClaim(AttestationClaim):
     """Class representing PSA Attestation Token Software Component Measurement description claim"""
     def get_claim_key(self=None):
         return SW_COMPONENT_RANGE + 6
diff --git a/iat-verifier/iatverifier/psa_iot_profile1_token_verifier.py b/iat-verifier/iatverifier/psa_iot_profile1_token_verifier.py
index ae1b4f4..96f721e 100644
--- a/iat-verifier/iatverifier/psa_iot_profile1_token_verifier.py
+++ b/iat-verifier/iatverifier/psa_iot_profile1_token_verifier.py
@@ -65,7 +65,6 @@
                 'verifier': self,
                 'claims': sw_component_claims,
                 'is_list': True,
-                'cross_claim_requirement_checker': None,
                 'necessity': Claim.OPTIONAL}),
             (NoMeasurementsClaim, {'verifier': self, 'necessity': Claim.OPTIONAL}),
             (ChallengeClaim, {'verifier': self, 'necessity': Claim.MANDATORY}),
@@ -82,18 +81,21 @@
             cose_alg=cose_alg,
             signing_key=signing_key)
 
-    @staticmethod
-    def check_cross_claim_requirements(verifier, claims):
-        if SWComponentsClaim.get_claim_key() in claims:
-            sw_component_present = claims[SWComponentsClaim.get_claim_key()].verify_count > 0
-        else:
-            sw_component_present = False
+    def verify(self, token_item):
 
-        if NoMeasurementsClaim.get_claim_key() in claims:
-            no_measurement_present = claims[NoMeasurementsClaim.get_claim_key()].verify_count > 0
-        else:
-            no_measurement_present = False
+        root_claims_token_item = token_item.value
+        root_claims_dict = root_claims_token_item.value
+
+        assert(isinstance(root_claims_dict, dict))
+
+        sw_component_present = False
+        no_measurement_present = False
+        for claim_token_item in root_claims_dict.values():
+            if (isinstance(claim_token_item.claim_type, SWComponentsClaim)):
+                sw_component_present = True
+            if (isinstance(claim_token_item.claim_type, NoMeasurementsClaim)):
+                no_measurement_present = True
 
         if not sw_component_present and not no_measurement_present:
-            verifier.error('Invalid IAT: no software measurements defined and '
-                  'NO_MEASUREMENTS claim is not present.')
+            self.error('Invalid IAT: no software measurements defined and '
+                  'NO_MEASUREMENTS claim is not present.')
\ No newline at end of file
diff --git a/iat-verifier/scripts/check_iat b/iat-verifier/scripts/check_iat
index 80a1dfc..f8b1279 100755
--- a/iat-verifier/scripts/check_iat
+++ b/iat-verifier/scripts/check_iat
@@ -153,9 +153,9 @@
         with open(args.tokenfile, 'rb') as token_file:
             token = verifier.parse_token(
                 token=token_file.read(),
-                verify=True,
                 check_p_header=args.check_protected_header,
                 lower_case_key=False)
+            token.verify()
         if key_checked:
             print('Signature OK')
         print('Token format OK')
@@ -165,7 +165,8 @@
 
     if args.print_iat:
         print('Token:')
-        json.dump(recursive_bytes_to_strings(token, in_place=True),
+        token_map = token.get_token_map()
+        json.dump(recursive_bytes_to_strings(token_map, in_place=True),
                   sys.stdout, indent=4)
         print('')
 
diff --git a/iat-verifier/scripts/decompile_token b/iat-verifier/scripts/decompile_token
index c9b7f37..491263b 100755
--- a/iat-verifier/scripts/decompile_token
+++ b/iat-verifier/scripts/decompile_token
@@ -80,9 +80,8 @@
     with open(args.source, 'rb') as fh:
         token_map = verifier.parse_token(
             token=fh.read(),
-            verify=False,
             check_p_header=False,
-            lower_case_key=True)
+            lower_case_key=True).get_token_map()
 
     if args.outfile:
         with open(args.outfile, 'w', encoding="UTF-8") as wfh:
diff --git a/iat-verifier/tests/synthetic_token_claims.py b/iat-verifier/tests/synthetic_token_claims.py
index 992c9ba..73e3ffe 100644
--- a/iat-verifier/tests/synthetic_token_claims.py
+++ b/iat-verifier/tests/synthetic_token_claims.py
@@ -23,17 +23,15 @@
     def get_claim_name(self=None):
         return 'SYN_CLAIM_INT'
 
-    def verify(self, value):
-        self._check_type(self.get_claim_name(), value, int)
-        self.verify_count += 1
+    def verify(self, token_item):
+        self._check_type(self.get_claim_name(), token_item.value, int)
 
 class SynBoxesClaim(CompositeAttestClaim):
-    """A composite claim with a cross claim checker."""
+    """A claim that contains other claims with a verifier"""
     def __init__(self, verifier, *, claims, is_list, necessity=AttestationClaim.MANDATORY):
         super().__init__(
             verifier=verifier,
             claims=claims,
-            cross_claim_requirement_checker=type(self).check_cross_claim_requirements,
             is_list=is_list,
             necessity=necessity)
 
@@ -43,19 +41,19 @@
     def get_claim_name(self=None):
         return 'SYN_BOXES'
 
-    @staticmethod
-    def check_cross_claim_requirements(verifier, claims):
+    def verify(self, token_item):
         """Checking the claims for a box.
 
         A box must have either all its dimensions defined, or none of them
         """
-        box_size_prop_count = 0
-        for c in [BoxWidthClaim, BoxHeightClaim, BoxDepthClaim]:
-            if claims[c.get_claim_key()].claim_found():
-                box_size_prop_count += 1
+        for box in token_item.value:
+            box_size_prop_count = 0
+            for c in [BoxWidthClaim, BoxHeightClaim, BoxDepthClaim]:
+                if c.get_claim_name() in box.keys():
+                    box_size_prop_count += 1
 
-        if box_size_prop_count not in (0, 3):
-            verifier.error('Invalid IAT: Box size must have all 3 dimensions')
+            if box_size_prop_count not in (0, 3):
+                self.verifier.error('Invalid IAT: Box size must have all 3 dimensions')
 
 class BoxWidthClaim(AttestationClaim):
     """A simple claim that has an int value."""
@@ -65,9 +63,8 @@
     def get_claim_name(self=None):
         return 'BOX_WIDTH'
 
-    def verify(self, value):
-        self._check_type(self.get_claim_name(), value, int)
-        self.verify_count += 1
+    def verify(self, token_item):
+        self._check_type(self.get_claim_name(), token_item.value, int)
 
 class BoxHeightClaim(AttestationClaim):
     """A simple claim that has an int value."""
@@ -77,9 +74,8 @@
     def get_claim_name(self=None):
         return 'BOX_HEIGHT'
 
-    def verify(self, value):
-        self._check_type(self.get_claim_name(), value, int)
-        self.verify_count += 1
+    def verify(self, token_item):
+        self._check_type(self.get_claim_name(), token_item.value, int)
 
 class BoxDepthClaim(AttestationClaim):
     """A simple claim that has an int value."""
@@ -89,9 +85,8 @@
     def get_claim_name(self=None):
         return 'BOX_DEPTH'
 
-    def verify(self, value):
-        self._check_type(self.get_claim_name(), value, int)
-        self.verify_count += 1
+    def verify(self, token_item):
+        self._check_type(self.get_claim_name(), token_item.value, int)
 
 class BoxColorClaim(AttestationClaim):
     """A simple claim that has a string value."""
@@ -101,9 +96,8 @@
     def get_claim_name(self=None):
         return 'BOX_COLOR'
 
-    def verify(self, value):
-        self._check_type(self.get_claim_name(), value, str)
-        self.verify_count += 1
+    def verify(self, token_item):
+        self._check_type(self.get_claim_name(), token_item.value, str)
 
     @classmethod
     def is_utf_8(cls):
diff --git a/iat-verifier/tests/synthetic_token_verifier.py b/iat-verifier/tests/synthetic_token_verifier.py
index a8ac500..96d9a8c 100644
--- a/iat-verifier/tests/synthetic_token_verifier.py
+++ b/iat-verifier/tests/synthetic_token_verifier.py
@@ -85,10 +85,6 @@
             configuration=configuration,
             necessity=Claim.MANDATORY)
 
-    @staticmethod
-    def check_cross_claim_requirements(verifier, claims):
-        pass
-
 class SyntheticTokenVerifier2(Verifier):
     """Another test token that may contain other tokens"""
     def get_claim_key(self=None):
@@ -165,10 +161,6 @@
             configuration=configuration,
             necessity=Claim.MANDATORY)
 
-    @staticmethod
-    def check_cross_claim_requirements(verifier, claims):
-        pass
-
 class SyntheticInternalTokenVerifier(Verifier):
     """A Test token that is intended to use inside another token"""
 
@@ -211,11 +203,6 @@
             configuration=configuration,
             necessity=necessity)
 
-    @staticmethod
-    def check_cross_claim_requirements(verifier, claims):
-        pass
-
-
 class SyntheticInternalTokenVerifier2(Verifier):
     """Another Test token that is intended to use inside another token"""
 
@@ -250,7 +237,3 @@
             claims=claims,
             configuration=configuration,
             necessity=necessity)
-
-    @staticmethod
-    def check_cross_claim_requirements(verifier, claims):
-        pass
diff --git a/iat-verifier/tests/test_synthetic.py b/iat-verifier/tests/test_synthetic.py
index 37775f3..413703d 100644
--- a/iat-verifier/tests/test_synthetic.py
+++ b/iat-verifier/tests/test_synthetic.py
@@ -213,9 +213,9 @@
                 'unknown_claims.cbor',
                 test_verifier)
         self.assertEquals(4, len(test_ctx.output))
-        self.assertIn('Unexpected TOKEN_CLAIM claim: 9901, skipping', test_ctx.output[0])
+        self.assertIn('Unexpected TOKEN_ROOT_CLAIMS claim: 9901, skipping', test_ctx.output[0])
         self.assertIn('Unexpected SYN_BOXES claim: 9902, skipping', test_ctx.output[1])
-        self.assertIn('Unexpected TOKEN_CLAIM claim: 9903, skipping', test_ctx.output[2])
+        self.assertIn('Unexpected TOKEN_ROOT_CLAIMS claim: 9903, skipping', test_ctx.output[2])
         self.assertIn('Unexpected SYN_BOXES claim: 9904, skipping', test_ctx.output[3])
 
         config = VerifierConfiguration(keep_going=True, strict=True)
@@ -232,9 +232,9 @@
                 'unknown_claims.cbor',
                 test_verifier)
         self.assertEquals(4, len(test_ctx.output))
-        self.assertIn('ERROR:iat-verifiers:Unexpected TOKEN_CLAIM claim: 9901', test_ctx.output[0])
+        self.assertIn('ERROR:iat-verifiers:Unexpected TOKEN_ROOT_CLAIMS claim: 9901', test_ctx.output[0])
         self.assertIn('ERROR:iat-verifiers:Unexpected SYN_BOXES claim: 9902', test_ctx.output[1])
-        self.assertIn('ERROR:iat-verifiers:Unexpected TOKEN_CLAIM claim: 9903', test_ctx.output[2])
+        self.assertIn('ERROR:iat-verifiers:Unexpected TOKEN_ROOT_CLAIMS claim: 9903', test_ctx.output[2])
         self.assertIn('ERROR:iat-verifiers:Unexpected SYN_BOXES claim: 9904', test_ctx.output[3])
 
         config = VerifierConfiguration(keep_going=False, strict=False)
@@ -250,9 +250,9 @@
                 DATA_DIR,
                 'unknown_claims.cbor',
                 test_verifier)
-        self.assertIn('Unexpected TOKEN_CLAIM claim: 9901, skipping', test_ctx.output[0])
+        self.assertIn('Unexpected TOKEN_ROOT_CLAIMS claim: 9901, skipping', test_ctx.output[0])
         self.assertIn('Unexpected SYN_BOXES claim: 9902, skipping', test_ctx.output[1])
-        self.assertIn('Unexpected TOKEN_CLAIM claim: 9903, skipping', test_ctx.output[2])
+        self.assertIn('Unexpected TOKEN_ROOT_CLAIMS claim: 9903, skipping', test_ctx.output[2])
         self.assertIn('Unexpected SYN_BOXES claim: 9904, skipping', test_ctx.output[3])
 
         config = VerifierConfiguration(keep_going=False, strict=True)
@@ -269,4 +269,4 @@
                 'unknown_claims.cbor',
                 test_verifier)
         self.assertIn(
-                'Unexpected TOKEN_CLAIM claim: 9901', test_ctx.exception.args[0])
+                'Unexpected TOKEN_ROOT_CLAIMS claim: 9901', test_ctx.exception.args[0])
diff --git a/iat-verifier/tests/test_utils.py b/iat-verifier/tests/test_utils.py
index 7c54ef6..e05c953 100644
--- a/iat-verifier/tests/test_utils.py
+++ b/iat-verifier/tests/test_utils.py
@@ -71,11 +71,12 @@
     """Read a cbor file and returns the parsed dictionary"""
     filepath = os.path.join(data_dir, filename)
     with open(filepath, 'rb') as file:
-        return verifier.parse_token(
+        token_item = verifier.parse_token(
             token=file.read(),
-            verify=True,
             check_p_header=check_p_header,
             lower_case_key=False)
+    token_item.verify()
+    return token_item
 
 def create_and_read_iat(data_dir, source_name, verifier):
     """Read a yaml file, compile it into a cbor token, and read it back"""
diff --git a/iat-verifier/tests/test_verifier.py b/iat-verifier/tests/test_verifier.py
index ed3a725..bb03513 100644
--- a/iat-verifier/tests/test_verifier.py
+++ b/iat-verifier/tests/test_verifier.py
@@ -58,20 +58,19 @@
         #dump_file_binary(good_sig)
 
         with open(good_sig, 'rb') as wfh:
-            verifier_good_sig.parse_token(
-                token=wfh.read(),
-                verify=True,
-                check_p_header=False,
-                lower_case_key=False)
-
+            token_item = verifier_good_sig.parse_token(
+                         token=wfh.read(),
+                         check_p_header=False,
+                         lower_case_key=False)
+            token_item.verify()
 
         with self.assertRaises(ValueError) as test_ctx:
             with open(bad_sig, 'rb') as wfh:
-                verifier_good_sig.parse_token(
+                token_item = verifier_good_sig.parse_token(
                     token=wfh.read(),
-                    verify=True,
                     check_p_header=False,
                     lower_case_key=False)
+                token_item.verify()
 
         self.assertIn('Bad signature', test_ctx.exception.args[0])
 
@@ -223,7 +222,7 @@
             PSAIoTProfile1TokenVerifier(method=method,
             cose_alg=cose_alg,
             signing_key=signing_key,
-            configuration=self.config))
+            configuration=self.config)).get_token_map()
         self.assertEqual(iat['SECURITY_LIFECYCLE'], 'SL_SECURED')
 
     def test_security_lifecycle_decoding(self):
@@ -237,5 +236,5 @@
             PSAIoTProfile1TokenVerifier(method=method,
             cose_alg=cose_alg,
             signing_key=signing_key,
-            configuration=self.config))
+            configuration=self.config)).get_token_map()
         self.assertEqual(iat['SECURITY_LIFECYCLE'], 'SL_SECURED')