Boot: Add security counter to image manifest

Add command line argument to the imgtool that can be used to add a
security counter TLV to the image manifest. This security counter value
can be used in rollback protection to compare the new image's security
counter against the active image's counter. It can be independent from
the image version, but if it is not specified in the argument list then
the script will generate it from the image version number
(not including the build number).

The value of the security counter is security critical data. Therefore,
this part of the TLV area must be included in the integrity protected
part of the image.

Add security counter to the build system. It can be specified at build
time with "-DSECURITY_COUNTER=<value>", otherwise the generated
security counter value will be added to the signed image.

Change-Id: Ia9773ad7a57fc3a8cc022e1c1df4321e27c912ec
Signed-off-by: David Vincze <david.vincze@arm.com>
diff --git a/bl2/ext/mcuboot/MCUBoot.cmake b/bl2/ext/mcuboot/MCUBoot.cmake
index 3f2a49a..95f5ff0 100644
--- a/bl2/ext/mcuboot/MCUBoot.cmake
+++ b/bl2/ext/mcuboot/MCUBoot.cmake
@@ -1,5 +1,5 @@
 #-------------------------------------------------------------------------------
-# Copyright (c) 2018, Arm Limited. All rights reserved.
+# Copyright (c) 2018-2019, Arm Limited. All rights reserved.
 #
 # SPDX-License-Identifier: BSD-3-Clause
 #
@@ -47,6 +47,12 @@
 		message(FATAL_ERROR "ERROR: Incomplete Configuration: FLASH_LAYOUT is not defined.")
 	endif()
 
+	if (DEFINED SECURITY_COUNTER)
+		set (ADD_SECURITY_COUNTER "-s ${SECURITY_COUNTER}")
+	else()
+		set (ADD_SECURITY_COUNTER "")
+	endif()
+
 	add_custom_command(TARGET ${_MY_PARAMS_NS_BIN}
 						POST_BUILD
 						#Create concatenated binary image from the two binary file
@@ -63,6 +69,7 @@
 							 -k ${MCUBOOT_DIR}/root-rsa-2048.pem
 							 --align 1
 							 -v ${IMAGE_VERSION}
+							 ${ADD_SECURITY_COUNTER}
 							 -H 0x400
 							 --pad ${SIGN_BIN_SIZE}
 							 ${CMAKE_BINARY_DIR}/${_MY_PARAMS_FULL_BIN}.bin
@@ -91,4 +98,4 @@
 	install(FILES  ${CMAKE_BINARY_DIR}/${_MY_PARAMS_SIGN_BIN}.bin
 			RENAME ${TFM_SIGN_NAME}${_MY_PARAMS_POSTFIX}.bin
 			DESTINATION outputs/fvp/)
-endfunction()
\ No newline at end of file
+endfunction()
diff --git a/bl2/ext/mcuboot/bootutil/include/bootutil/image.h b/bl2/ext/mcuboot/bootutil/include/bootutil/image.h
index 306ab31..871f3fb 100644
--- a/bl2/ext/mcuboot/bootutil/include/bootutil/image.h
+++ b/bl2/ext/mcuboot/bootutil/include/bootutil/image.h
@@ -19,8 +19,9 @@
 
 /*
  * Original code taken from mcuboot project at:
- * https://github.com/runtimeco/mcuboot
- * Modifications are Copyright (c) 2018 Arm Limited.
+ * https://github.com/JuulLabs-OSS/mcuboot
+ * Git SHA of the original version: b5b59f16a5768c5175cf6c7ab082e84a5843f06f
+ * Modifications are Copyright (c) 2018-2019 Arm Limited.
  */
 
 #ifndef H_IMAGE_
@@ -66,6 +67,7 @@
 #define IMAGE_TLV_KEYHASH           0x01   /* hash of the public key */
 #define IMAGE_TLV_SHA256            0x10   /* SHA256 of image hdr and body */
 #define IMAGE_TLV_RSA2048_PSS       0x20   /* RSA2048 of hash output */
+#define IMAGE_TLV_SEC_CNT           0x50   /* security counter */
 
 #define IMAGE_VER_MAJOR_LENGTH      8
 #define IMAGE_VER_MINOR_LENGTH      8
@@ -83,12 +85,12 @@
 struct image_header {
     uint32_t ih_magic;
     uint32_t ih_load_addr;
-    uint16_t ih_hdr_size; /* Size of image header (bytes). */
-    uint16_t _pad1;
-    uint32_t ih_img_size; /* Does not include header. */
-    uint32_t ih_flags;    /* IMAGE_F_[...]. */
+    uint16_t ih_hdr_size;            /* Size of image header (bytes). */
+    uint16_t ih_protect_tlv_size;    /* Size of protected TLV area (bytes). */
+    uint32_t ih_img_size;            /* Does not include header. */
+    uint32_t ih_flags;               /* IMAGE_F_[...]. */
     struct image_version ih_ver;
-    uint32_t _pad2;
+    uint32_t _pad1;
 };
 
 /** Image TLV header.  All fields in little endian. */
diff --git a/bl2/ext/mcuboot/bootutil/src/image_validate.c b/bl2/ext/mcuboot/bootutil/src/image_validate.c
index 5cb4b49..68f1c40 100644
--- a/bl2/ext/mcuboot/bootutil/src/image_validate.c
+++ b/bl2/ext/mcuboot/bootutil/src/image_validate.c
@@ -63,11 +63,17 @@
         bootutil_sha256_update(&sha256_ctx, seed, seed_len);
     }
 
-    /*
-     * Hash is computed over image header and image itself. No TLV is
-     * included ATM.
-     */
+    /* Hash is computed over image header and image itself. */
     size = hdr->ih_img_size + hdr->ih_hdr_size;
+
+    /* If a security counter TLV is present then the TLV info header and the
+     * security counter are also protected and must be included in the hash
+     * calculation.
+     */
+    if (hdr->ih_protect_tlv_size != 0) {
+        size += hdr->ih_protect_tlv_size;
+    }
+
     for (off = 0; off < size; off += blk_sz) {
         blk_sz = size - off;
         if (blk_sz > tmp_buf_sz) {
@@ -229,7 +235,6 @@
     }
 
     /* The TLVs come after the image. */
-    /* After image there are TLVs. */
     off = hdr->ih_img_size + hdr->ih_hdr_size;
 
     rc = flash_area_read(fap, off, &info, sizeof(info));
diff --git a/bl2/ext/mcuboot/scripts/imgtool.py b/bl2/ext/mcuboot/scripts/imgtool.py
index b984ce2..924fa96 100644
--- a/bl2/ext/mcuboot/scripts/imgtool.py
+++ b/bl2/ext/mcuboot/scripts/imgtool.py
@@ -1,7 +1,7 @@
 #! /usr/bin/env python3
 #
 # Copyright 2017 Linaro Limited
-# Copyright (c) 2018, Arm Limited.
+# Copyright (c) 2018-2019, Arm Limited.
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
 # you may not use this file except in compliance with the License.
@@ -102,13 +102,24 @@
 def do_sign(args):
     if args.rsa_pkcs1_15:
         keys.sign_rsa_pss = False
+
+    version_num = next_version_number(args,
+                                      version.decode_version("0"),
+                                      "lastVerNum.txt")
+
+    if args.security_counter is None:
+        # Security counter has not been explicitly provided,
+        # generate it from the version number
+        args.security_counter = ((version_num.major << 24)
+                                 + (version_num.minor << 16)
+                                 + version_num.revision)
+
     img = image.Image.load(args.infile,
-            version=next_version_number(args,
-                                        version.decode_version("0"),
-                                        "lastVerNum.txt"),
-            header_size=args.header_size,
-            included_header=args.included_header,
-            pad=args.pad)
+                           version=version_num,
+                           header_size=args.header_size,
+                           security_cnt=args.security_counter,
+                           included_header=args.included_header,
+                           pad=args.pad)
     key = keys.load(args.key) if args.key else None
     img.sign(key, find_load_address(args))
 
@@ -155,6 +166,8 @@
     sign.add_argument("--align", type=alignment_value, required=True)
     sign.add_argument("-v", "--version", type=version.decode_version,
                       default="0.0.0+0")
+    sign.add_argument("-s", "--security-counter", type=intparse,
+                      help='Specify explicitly the security counter value')
     sign.add_argument("-H", "--header-size", type=intparse, required=True)
     sign.add_argument("--included-header", default=False, action='store_true',
                       help='Image has gap for header')
@@ -174,4 +187,4 @@
     subcmds[args.subcmd](args)
 
 if __name__ == '__main__':
-    args()
\ No newline at end of file
+    args()
diff --git a/bl2/ext/mcuboot/scripts/imgtool_lib/image.py b/bl2/ext/mcuboot/scripts/imgtool_lib/image.py
index 67425d4..bd1bf5d 100644
--- a/bl2/ext/mcuboot/scripts/imgtool_lib/image.py
+++ b/bl2/ext/mcuboot/scripts/imgtool_lib/image.py
@@ -1,5 +1,5 @@
 # Copyright 2017 Linaro Limited
-# Copyright (c) 2018, Arm Limited.
+# Copyright (c) 2018-2019, Arm Limited.
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
 # you may not use this file except in compliance with the License.
@@ -23,6 +23,9 @@
 
 IMAGE_MAGIC = 0x96f3b83d
 IMAGE_HEADER_SIZE = 32
+TLV_HEADER_SIZE = 4
+PAYLOAD_DIGEST_SIZE = 32  # SHA256 hash
+KEYHASH_SIZE = 32
 
 # Image header flags.
 IMAGE_F = {
@@ -32,7 +35,8 @@
 TLV_VALUES = {
         'KEYHASH': 0x01,
         'SHA256' : 0x10,
-        'RSA2048': 0x20, }
+        'RSA2048': 0x20,
+        'SEC_CNT': 0x50, }
 
 TLV_INFO_SIZE = 4
 TLV_INFO_MAGIC = 0x6907
@@ -79,15 +83,19 @@
         obj.check()
         return obj
 
-    def __init__(self, version, header_size=IMAGE_HEADER_SIZE, pad=0):
+    def __init__(self, version, header_size=IMAGE_HEADER_SIZE, security_cnt=0,
+                 pad=0):
         self.version = version
         self.header_size = header_size or IMAGE_HEADER_SIZE
+        self.security_cnt = security_cnt
         self.pad = pad
 
     def __repr__(self):
-        return "<Image version={}, header_size={}, pad={}, payloadlen=0x{:x}>".format(
+        return "<Image version={}, header_size={}, security_counter={}, \
+                 pad={}, payloadlen=0x{:x}>".format(
                 self.version,
                 self.header_size,
+                self.security_cnt,
                 self.pad,
                 len(self.payload))
 
@@ -104,10 +112,26 @@
                 raise Exception("Padding requested, but image does not start with zeros")
 
     def sign(self, key, ramLoadAddress):
-        self.add_header(key, ramLoadAddress)
+        # Size of the security counter TLV:
+        # header ('BBH') + payload ('I') = 8 Bytes
+        protected_tlv_size = TLV_INFO_SIZE + 8
+
+        self.add_header(key, protected_tlv_size, ramLoadAddress)
 
         tlv = TLV()
 
+        payload = struct.pack('I', self.security_cnt)
+        tlv.add('SEC_CNT', payload)
+        # Full TLV size needs to be calculated in advance, because the
+        # header will be protected as well
+        full_size = (TLV_INFO_SIZE + len(tlv.buf) + TLV_HEADER_SIZE
+                     + PAYLOAD_DIGEST_SIZE)
+        if key is not None:
+            full_size += (TLV_HEADER_SIZE + KEYHASH_SIZE
+                          + TLV_HEADER_SIZE + key.sig_len())
+        tlv_header = struct.pack('HH', TLV_INFO_MAGIC, full_size)
+        self.payload += tlv_header + bytes(tlv.buf)
+
         sha = hashlib.sha256()
         sha.update(self.payload)
         digest = sha.digest()
@@ -124,9 +148,9 @@
             sig = key.sign(self.payload)
             tlv.add(key.sig_tlv(), sig)
 
-        self.payload += tlv.get()
+        self.payload += tlv.get()[protected_tlv_size:]
 
-    def add_header(self, key, ramLoadAddress):
+    def add_header(self, key, protected_tlv_size, ramLoadAddress):
         """Install the image header.
 
         The key is needed to know the type of signature, and
@@ -140,28 +164,28 @@
 
         fmt = ('<' +
             # type ImageHdr struct {
-            'I' +   # Magic uint32
-            'I' +   # LoadAddr uint32
-            'H' +   # HdrSz uint16
-            'H' +   # Pad1  uint16
-            'I' +   # ImgSz uint32
-            'I' +   # Flags uint32
-            'BBHI' + # Vers  ImageVersion
-            'I'     # Pad2  uint32
+            'I' +    # Magic    uint32
+            'I' +    # LoadAddr uint32
+            'H' +    # HdrSz    uint16
+            'H' +    # PTLVSz   uint16
+            'I' +    # ImgSz    uint32
+            'I' +    # Flags    uint32
+            'BBHI' + # Vers     ImageVersion
+            'I'      # Pad1     uint32
             ) # }
         assert struct.calcsize(fmt) == IMAGE_HEADER_SIZE
         header = struct.pack(fmt,
                 IMAGE_MAGIC,
                 0 if (ramLoadAddress is None) else ramLoadAddress, # LoadAddr
                 self.header_size,
-                0, # Pad1
+                protected_tlv_size,  # TLV info header + security counter TLV
                 len(self.payload) - self.header_size, # ImageSz
                 flags, # Flags
                 self.version.major,
                 self.version.minor or 0,
                 self.version.revision or 0,
                 self.version.build or 0,
-                0) # Pad2
+                0)  # Pad1
         self.payload = bytearray(self.payload)
         self.payload[:len(header)] = header
 
@@ -176,4 +200,4 @@
         pbytes  = b'\xff' * padding
         pbytes += b'\xff' * (tsize - len(boot_magic))
         pbytes += boot_magic
-        self.payload += pbytes
\ No newline at end of file
+        self.payload += pbytes