imgtool: Add security counter to image manifest

Optionally add new security counter TLV to the protected image manifest
and also introduce a new command line option for the imgtool to specify
the value of this counter. The security counter can be used in rollback
protection to compare the new image's security counter against the
active counter value. Its value can be independent from the image
version, but if the 'auto' keyword is passed in the argument list of the
script then it will be generated from the version number (not including
the build number).

The value of the security counter is security critical data. Therefore,
it must be part of the protected TLV area.

Change-Id: I45926d22364d0528164f50fa379abf050bdf65ff
Signed-off-by: David Vincze <david.vincze@arm.com>
diff --git a/scripts/imgtool/image.py b/scripts/imgtool/image.py
index 2ad5538..adba2b1 100644
--- a/scripts/imgtool/image.py
+++ b/scripts/imgtool/image.py
@@ -1,6 +1,6 @@
 # Copyright 2018 Nordic Semiconductor ASA
 # Copyright 2017 Linaro Limited
-# Copyright 2019 Arm Limited
+# Copyright 2019-2020 Arm Limited
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
 # you may not use this file except in compliance with the License.
@@ -61,7 +61,8 @@
         'ENCRSA2048': 0x30,
         'ENCKW128': 0x31,
         'ENCEC256': 0x32,
-        'DEPENDENCY': 0x40
+        'DEPENDENCY': 0x40,
+        'SEC_CNT': 0x50,
 }
 
 TLV_SIZE = 4
@@ -119,7 +120,7 @@
                  pad_header=False, pad=False, align=1, slot_size=0,
                  max_sectors=DEFAULT_MAX_SECTORS, overwrite_only=False,
                  endian="little", load_addr=0, erased_val=None,
-                 save_enctlv=False):
+                 save_enctlv=False, security_counter=None):
         self.version = version or versmod.decode_version("0")
         self.header_size = header_size
         self.pad_header = pad_header
@@ -137,12 +138,23 @@
         self.save_enctlv = save_enctlv
         self.enctlv_len = 0
 
+        if security_counter == 'auto':
+            # Security counter has not been explicitly provided,
+            # generate it from the version number
+            self.security_counter = ((self.version.major << 24)
+                                     + (self.version.minor << 16)
+                                     + self.version.revision)
+        else:
+            self.security_counter = security_counter
+
     def __repr__(self):
-        return "<Image version={}, header_size={}, base_addr={}, load_addr={}, \
-                align={}, slot_size={}, max_sectors={}, overwrite_only={}, \
-                endian={} format={}, payloadlen=0x{:x}>".format(
+        return "<Image version={}, header_size={}, security_counter={}, \
+                base_addr={}, load_addr={}, align={}, slot_size={}, \
+                max_sectors={}, overwrite_only={}, endian={} format={}, \
+                payloadlen=0x{:x}>".format(
                     self.version,
                     self.header_size,
+                    self.security_counter,
                     self.base_addr if self.base_addr is not None else "N/A",
                     self.load_addr,
                     self.align,
@@ -246,14 +258,22 @@
     def create(self, key, enckey, dependencies=None):
         self.enckey = enckey
 
-        if dependencies is None:
-            dependencies_num = 0
-            protected_tlv_size = 0
-        else:
-            # Size of a Dependency TLV = Header ('BBH') + Payload('IBBHI')
-            # = 16 Bytes
+        protected_tlv_size = 0
+
+        if self.security_counter is not None:
+            # Size of the security counter TLV: header ('HH') + payload ('I')
+            #                                   = 4 + 4 = 8 Bytes
+            protected_tlv_size += TLV_SIZE + 4
+
+        if dependencies is not None:
+            # Size of a Dependency TLV = Header ('HH') + Payload('IBBHI')
+            # = 4 + 12 = 16 Bytes
             dependencies_num = len(dependencies[DEP_IMAGES_KEY])
-            protected_tlv_size = (dependencies_num * 16) + TLV_INFO_SIZE
+            protected_tlv_size += (dependencies_num * 16)
+
+        if protected_tlv_size != 0:
+            # Add the size of the TLV info header
+            protected_tlv_size += TLV_INFO_SIZE
 
         # At this point the image is already on the payload, this adds
         # the header to the payload as well
@@ -265,17 +285,24 @@
         # in the hash calculation
         protected_tlv_off = None
         if protected_tlv_size != 0:
-            for i in range(dependencies_num):
-                e = STRUCT_ENDIAN_DICT[self.endian]
-                payload = struct.pack(
-                                e + 'B3x'+'BBHI',
-                                int(dependencies[DEP_IMAGES_KEY][i]),
-                                dependencies[DEP_VERSIONS_KEY][i].major,
-                                dependencies[DEP_VERSIONS_KEY][i].minor,
-                                dependencies[DEP_VERSIONS_KEY][i].revision,
-                                dependencies[DEP_VERSIONS_KEY][i].build
-                                )
-                prot_tlv.add('DEPENDENCY', payload)
+
+            e = STRUCT_ENDIAN_DICT[self.endian]
+
+            if self.security_counter is not None:
+                payload = struct.pack(e + 'I', self.security_counter)
+                prot_tlv.add('SEC_CNT', payload)
+
+            if dependencies is not None:
+                for i in range(dependencies_num):
+                    payload = struct.pack(
+                                    e + 'B3x'+'BBHI',
+                                    int(dependencies[DEP_IMAGES_KEY][i]),
+                                    dependencies[DEP_VERSIONS_KEY][i].major,
+                                    dependencies[DEP_VERSIONS_KEY][i].minor,
+                                    dependencies[DEP_VERSIONS_KEY][i].revision,
+                                    dependencies[DEP_VERSIONS_KEY][i].build
+                                    )
+                    prot_tlv.add('DEPENDENCY', payload)
 
             protected_tlv_off = len(self.payload)
             self.payload += prot_tlv.get()