sign_encrypt.py: add subkey support

Adds support to sign TAs using a subkey. Two new commands are added to
help with this:
- sign-subkey: signs a new subkey using either the root key or the keys
from another subkey
- subkey-uuid: calculate the UUID of next TA or subkey

Acked-by: Jerome Forissier <jerome.forissier@linaro.org>
Acked-by: Etienne Carriere <etienne.carriere@linaro.org>
Signed-off-by: Jens Wiklander <jens.wiklander@linaro.org>
diff --git a/scripts/sign_encrypt.py b/scripts/sign_encrypt.py
index b8ac2c0..e31b65f 100755
--- a/scripts/sign_encrypt.py
+++ b/scripts/sign_encrypt.py
@@ -16,10 +16,15 @@
 enc_key_type = {'SHDR_ENC_KEY_DEV_SPECIFIC': 0x0,
                 'SHDR_ENC_KEY_CLASS_WIDE': 0x1}
 
+TEE_ATTR_RSA_MODULUS = 0xD0000130
+TEE_ATTR_RSA_PUBLIC_EXPONENT = 0xD0000230
+
 SHDR_BOOTSTRAP_TA = 1
 SHDR_ENCRYPTED_TA = 2
+SHDR_SUBKEY = 3
 SHDR_MAGIC = 0x4f545348
 SHDR_SIZE = 20
+SK_HDR_SIZE = 20
 EHDR_SIZE = 12
 UUID_SIZE = 16
 # Use 12 bytes for nonce per recommendation
@@ -33,6 +38,20 @@
             return k
 
 
+def uuid_v5_sha512(namespace_bytes, name):
+    from cryptography.hazmat.primitives import hashes
+    from uuid import UUID
+
+    h = hashes.Hash(hashes.SHA512())
+    h.update(namespace_bytes + bytes(name, 'utf-8'))
+    digest = h.finalize()
+    return UUID(bytes=digest[:16], version=5)
+
+
+def name_img_to_str(name_img):
+    return name_img.decode().split('\x00', 1)[0]
+
+
 def uuid_parse(s):
     from uuid import UUID
     return UUID(s)
@@ -43,6 +62,17 @@
 
 
 def get_args():
+    import argparse
+    import textwrap
+
+    class OnlyOne(argparse.Action):
+        def __call__(self, parser, namespace, values, option_string=None):
+            a = self.dest + '_assigned'
+            if getattr(namespace, a, False):
+                raise argparse.ArgumentError(self, 'Can only be given once')
+            setattr(namespace, a, True)
+            setattr(namespace, self.dest, values)
+
     def arg_add_uuid(parser):
         parser.add_argument(
             '--uuid', required=True, type=uuid_parse,
@@ -103,6 +133,46 @@
                 The hash and signature algorithm.
                 Defaults to TEE_ALG_RSASSA_PKCS1_PSS_MGF1_SHA256.''')
 
+    def arg_add_subkey(parser):
+        parser.add_argument(
+            '--subkey', action=OnlyOne, help='Name of subkey input file')
+
+    def arg_add_name(parser):
+        parser.add_argument('--name',
+                            help='Input name for subspace of a subkey')
+
+    def arg_add_subkey_uuid_in(parser):
+        parser.add_argument(
+            '--in', required=True, dest='inf',
+            help='Name of subkey input file')
+
+    def arg_add_max_depth(parser):
+        parser.add_argument(
+            '--max-depth', required=False, type=int_parse, help='''
+            Max depth of subkeys below this subkey''')
+
+    def arg_add_name_size(parser):
+        parser.add_argument(
+            '--name-size', required=True, type=int_parse, help='''
+            Size of (unsigned) input name for subspace of a subkey.
+            Set to 0 to create an identity subkey (a subkey having
+            the same UUID as the next subkey or TA)''')
+
+    def arg_add_subkey_version(parser):
+        parser.add_argument(
+            '--subkey-version', required=False, type=int_parse, default=0,
+            help='Subkey version used for rollback protection')
+
+    def arg_add_subkey_in(parser):
+        parser.add_argument(
+            '--in', required=True, dest='inf', help='''
+            Name of PEM file with the public key of the new subkey''')
+
+    def arg_add_subkey_out(parser):
+        parser.add_argument(
+            '--out', required=True, dest='outf',
+            help='Name of subkey output file')
+
     def get_outf_default(parsed):
         return str(parsed.uuid) + '.ta'
 
@@ -119,9 +189,6 @@
         if hasattr(parsed, attr) and getattr(parsed, attr) is None:
             setattr(parsed, attr, func(parsed))
 
-    import argparse
-    import textwrap
-
     parser = argparse.ArgumentParser(
         description='Sign and encrypt (optional) a Trusted Application ' +
         ' for OP-TEE.',
@@ -140,6 +207,8 @@
     arg_add_in(parser_sign_enc)
     arg_add_out(parser_sign_enc)
     arg_add_key(parser_sign_enc)
+    arg_add_subkey(parser_sign_enc)
+    arg_add_name(parser_sign_enc)
     arg_add_enc_key(parser_sign_enc)
     arg_add_enc_key_type(parser_sign_enc)
     arg_add_algo(parser_sign_enc)
@@ -204,6 +273,28 @@
     parser_display.set_defaults(func=command_display)
     arg_add_in(parser_display)
 
+    parser_subkey_uuid = subparsers.add_parser(
+        'subkey-uuid', prog=parser.prog + ' subkey-uuid',
+        help='calculate the UUID of next TA or subkey')
+    parser_subkey_uuid.set_defaults(func=command_subkey_uuid)
+    arg_add_subkey_uuid_in(parser_subkey_uuid)
+    arg_add_name(parser_subkey_uuid)
+
+    parser_sign_subkey = subparsers.add_parser(
+        'sign-subkey', prog=parser.prog + ' sign-subkey',
+        help='Sign a subkey')
+    parser_sign_subkey.set_defaults(func=command_sign_subkey)
+    arg_add_name(parser_sign_subkey)
+    arg_add_subkey_in(parser_sign_subkey)
+    arg_add_uuid(parser_sign_subkey)
+    arg_add_key(parser_sign_subkey)
+    arg_add_subkey_out(parser_sign_subkey)
+    arg_add_max_depth(parser_sign_subkey)
+    arg_add_name_size(parser_sign_subkey)
+    arg_add_subkey(parser_sign_subkey)
+    arg_add_subkey_version(parser_sign_subkey)
+    arg_add_algo(parser_sign_subkey)
+
     argv = sys.argv[1:]
     if (len(argv) > 0 and argv[0][0] == '-' and
             argv[0] != '-h' and argv[0] != '--help'):
@@ -226,25 +317,25 @@
     return parsed
 
 
+def load_asymmetric_key_img(data):
+    from cryptography.hazmat.backends import default_backend
+    from cryptography.hazmat.primitives.serialization import (
+        load_pem_private_key, load_pem_public_key)
+
+    try:
+        return load_pem_private_key(data, password=None,
+                                    backend=default_backend())
+    except ValueError:
+        return load_pem_public_key(data, backend=default_backend())
+
+
 def load_asymmetric_key(arg_key):
     if arg_key.startswith('arn:'):
         from sign_helper_kms import _RSAPrivateKeyInKMS
-        key = _RSAPrivateKeyInKMS(arg_key)
+        return _RSAPrivateKeyInKMS(arg_key)
     else:
-        from cryptography.hazmat.backends import default_backend
-        from cryptography.hazmat.primitives.serialization import (
-            load_pem_private_key, load_pem_public_key)
-
         with open(arg_key, 'rb') as f:
-            data = f.read()
-
-            try:
-                key = load_pem_private_key(data, password=None,
-                                           backend=default_backend())
-            except ValueError:
-                key = load_pem_public_key(data, backend=default_backend())
-
-    return key
+            return load_asymmetric_key_img(f.read())
 
 
 class BinaryImage:
@@ -252,13 +343,19 @@
         from cryptography.hazmat.primitives import hashes
 
         # Exactly what inf is holding isn't determined a this stage
-        with open(arg_inf, 'rb') as f:
-            self.inf = f.read()
+        if isinstance(arg_inf, str):
+            with open(arg_inf, 'rb') as f:
+                self.inf = f.read()
+        else:
+            self.inf = arg_inf
 
         if arg_key is None:
             self.key = None
         else:
-            self.key = load_asymmetric_key(arg_key)
+            if isinstance(arg_key, str):
+                self.key = load_asymmetric_key(arg_key)
+            else:
+                self.key = arg_key
             self.sig_len = math.ceil(self.key.key_size / 8)
 
         self.chosen_hash = hashes.SHA256()
@@ -279,8 +376,9 @@
 
         h = hashes.Hash(self.chosen_hash, default_backend())
         h.update(self.shdr)
-        h.update(self.ta_uuid)
-        h.update(self.ta_version)
+        if hasattr(self, 'ta_uuid'):
+            h.update(self.ta_uuid)
+            h.update(self.ta_version)
         if hasattr(self, 'ehdr'):
             h.update(self.ehdr)
             h.update(self.nonce)
@@ -321,7 +419,58 @@
         self.ta_version = struct.pack('<I', ta_version)
         self.img_digest = self.__calc_digest()
 
+    def set_subkey(self, sign_algo, name, uuid, subkey_version, max_depth,
+                   name_size):
+        from cryptography.hazmat.primitives.asymmetric import rsa
+        import struct
+
+        self.subkey_name = name
+
+        subkey_key = load_asymmetric_key_img(self.inf)
+        if isinstance(subkey_key, rsa.RSAPrivateKey):
+            subkey_pkey = subkey_key.public_key()
+        else:
+            subkey_pkey = subkey_key
+
+        if max_depth is None:
+            if hasattr(self, 'previous_max_depth'):
+                if self.previous_max_depth <= 0:
+                    logger.error('Max depth of previous subkey is {}, '
+                                 .format(self.previous_max_depth) +
+                                 'cannot use a smaller value')
+                    sys.exit(1)
+
+                max_depth = self.previous_max_depth - 1
+            else:
+                max_depth = 0
+        else:
+            if (hasattr(self, 'previous_max_depth') and
+                    max_depth >= getattr(self, 'previous_max_depth')):
+                logger.error('Max depth of previous subkey is {} '
+                             .format(self.previous_max_depth) +
+                             'and the next value must be smaller')
+                sys.exit(1)
+
+        def int_to_bytes(x: int) -> bytes:
+            return x.to_bytes((x.bit_length() + 8) // 8, 'big')
+
+        n_bytes = int_to_bytes(subkey_pkey.public_numbers().n)
+        e_bytes = int_to_bytes(subkey_pkey.public_numbers().e)
+        attrs_end_offs = 16 + 5 * 4 + 2 * 3 * 4
+        shdr_subkey = struct.pack('<IIIIIIIIIII',
+                                  name_size, subkey_version,
+                                  max_depth, sig_tee_alg[sign_algo], 2,
+                                  TEE_ATTR_RSA_MODULUS,
+                                  attrs_end_offs, len(n_bytes),
+                                  TEE_ATTR_RSA_PUBLIC_EXPONENT,
+                                  attrs_end_offs + len(n_bytes),
+                                  len(e_bytes))
+        self.img = uuid.bytes + shdr_subkey + n_bytes + e_bytes
+        self.__pack_img(SHDR_SUBKEY, sign_algo)
+        self.img_digest = self.__calc_digest()
+
     def parse(self):
+        from cryptography.hazmat.primitives.asymmetric import rsa
         import struct
 
         offs = 0
@@ -373,11 +522,49 @@
                     raise Exception("Unexpected ciphertext size: ",
                                     "got {}, expected {}"
                                     .format(len(self.ciphertext), img_size))
+                self.img = self.ciphertext
             else:
                 self.img = self.inf[offs:]
                 if len(self.img) != img_size:
                     raise Exception("Unexpected img size: got {}, expected {}"
                                     .format(len(self.img), img_size))
+        elif img_type == SHDR_SUBKEY:
+            subkey_offs = offs
+            self.uuid = self.inf[offs:offs + UUID_SIZE]
+            offs += UUID_SIZE
+            self.subkey_hdr = self.inf[offs:offs + SK_HDR_SIZE]
+            [self.name_size, self.subkey_version, self.max_depth, self.algo,
+             self.attr_count] = struct.unpack('<IIIII', self.subkey_hdr)
+            offs += len(self.subkey_hdr)
+            self.attr = self.inf[offs:offs + img_size -
+                                 UUID_SIZE - len(self.subkey_hdr)]
+            offs += len(self.attr)
+            self.name_img = self.inf[offs:offs + self.name_size]
+            offs += self.name_size
+            self.next_inf = self.inf[offs:]
+
+            def find_attr(attr):
+                if self.attr_count <= 0:
+                    return None
+                for n in range(self.attr_count):
+                    o = subkey_offs + UUID_SIZE + SK_HDR_SIZE + n * 12
+                    [attr_value, attr_offs,
+                     attr_len] = struct.unpack('<III', self.inf[o: o + 12])
+                    if attr_value == attr:
+                        o = subkey_offs + attr_offs
+                        return self.inf[o:o + attr_len]
+                return None
+
+            n_bytes = find_attr(TEE_ATTR_RSA_MODULUS)
+            e_bytes = find_attr(TEE_ATTR_RSA_PUBLIC_EXPONENT)
+            e = int.from_bytes(e_bytes, 'big')
+            n = int.from_bytes(n_bytes, 'big')
+            self.subkey_key = rsa.RSAPublicNumbers(e, n).public_key()
+
+            self.img = self.inf[subkey_offs:offs - self.name_size]
+            if len(self.img) != img_size:
+                raise Exception("Unexpected img size: got {}, expected {}"
+                                .format(len(self.img), img_size))
         else:
             raise Exception("Unsupported image type: {}".format(img_type))
 
@@ -386,52 +573,10 @@
         import struct
         import uuid
 
-        offs = 0
-        shdr = self.inf[offs:offs + SHDR_SIZE]
-        [magic, img_type, img_size, algo_value, digest_len,
-         sig_len] = struct.unpack('<IIIIHH', shdr)
-        offs += SHDR_SIZE
-
-        if magic != SHDR_MAGIC:
-            Exception("Unexpected magic: 0x{:08x}".format(magic))
-
-        img_type_name = 'Unknown'
-        if img_type == SHDR_BOOTSTRAP_TA:
-            print('Bootstrap TA')
-            img_type_name = 'SHDR_BOOTSTRAP_TA'
-        if img_type == SHDR_ENCRYPTED_TA:
-            print('Encrypted TA')
-            img_type_name = 'SHDR_ENCRYPTED_TA'
-
-        algo_name = 'Unknown'
-        if algo_value in sig_tee_alg.values():
-            algo_name = value_to_key(sig_tee_alg, algo_value)
-
-        print(' struct shdr')
-        print('  magic:      0x{:08x}'.format(magic))
-        print('  img_type:   {} ({})'.format(img_type, img_type_name))
-        print('  img_size:   {} bytes'.format(img_size))
-        print('  algo:       0x{:08x} ({})'.format(algo_value, algo_name))
-        print('  hash_size:  {} bytes'.format(digest_len))
-        print('  sig_size:   {} bytes'.format(sig_len))
-
-        if algo_value not in sig_tee_alg.values():
-            raise Exception('Unrecognized algorithm: 0x{:08x}'
-                            .format(algo_value))
-
-        if digest_len != self.digest_len:
-            raise Exception("Unexpected digest len: {}".format(digest_len))
-
-        img_digest = self.inf[offs:offs + digest_len]
-        print('  hash:       {}'
-              .format(binascii.hexlify(img_digest).decode('ascii')))
-        offs += digest_len
-        sig = self.inf[offs:offs + sig_len]
-        offs += sig_len
-
-        if img_type == SHDR_BOOTSTRAP_TA or img_type == SHDR_ENCRYPTED_TA:
-            print(' struct shdr_bootstrap_ta')
+        def display_ta():
+            nonlocal offs
             ta_uuid = self.inf[offs:offs + UUID_SIZE]
+            print(' struct shdr_bootstrap_ta')
             print('  uuid:       {}'.format(uuid.UUID(bytes=ta_uuid)))
             offs += UUID_SIZE
             [ta_version] = struct.unpack('<I', self.inf[offs:offs + 4])
@@ -492,8 +637,85 @@
                 if len(img) != img_size:
                     raise Exception("Unexpected img size: got {}, expected {}"
                                     .format(len(img), img_size))
-        else:
-            raise Exception("Unsupported image type: {}".format(img_type))
+            offs += img_size
+
+        offs = 0
+        while offs < len(self.inf):
+            if offs > 0:
+                # name_size is the previous subkey header
+                name_img = self.inf[offs:offs + name_size]
+                print('  next name:  "{}"'.format(name_img_to_str(name_img)))
+                offs += name_size
+                print('Next header at offset: {} (0x{:x})'
+                      .format(offs, offs))
+
+            shdr = self.inf[offs:offs + SHDR_SIZE]
+            [magic, img_type, img_size, algo_value, digest_len,
+             sig_len] = struct.unpack('<IIIIHH', shdr)
+            offs += SHDR_SIZE
+
+            if magic != SHDR_MAGIC:
+                Exception("Unexpected magic: 0x{:08x}".format(magic))
+
+            img_type_name = 'Unknown'
+            if img_type == SHDR_BOOTSTRAP_TA:
+                print('Bootstrap TA')
+                img_type_name = 'SHDR_BOOTSTRAP_TA'
+            if img_type == SHDR_ENCRYPTED_TA:
+                print('Encrypted TA')
+                img_type_name = 'SHDR_ENCRYPTED_TA'
+            if img_type == SHDR_SUBKEY:
+                print('Subkey')
+                img_type_name = 'SHDR_SUBKEY'
+
+            algo_name = 'Unknown'
+            if algo_value in sig_tee_alg.values():
+                algo_name = value_to_key(sig_tee_alg, algo_value)
+
+            print(' struct shdr')
+            print('  magic:      0x{:08x}'.format(magic))
+            print('  img_type:   {} ({})'.format(img_type, img_type_name))
+            print('  img_size:   {} bytes'.format(img_size))
+            print('  algo:       0x{:08x} ({})'.format(algo_value, algo_name))
+            print('  hash_size:  {} bytes'.format(digest_len))
+            print('  sig_size:   {} bytes'.format(sig_len))
+
+            if algo_value not in sig_tee_alg.values():
+                raise Exception('Unrecognized algorithm: 0x{:08x}'
+                                .format(algo_value))
+
+            if digest_len != self.digest_len:
+                raise Exception("Unexpected digest len: {}".format(digest_len))
+
+            img_digest = self.inf[offs:offs + digest_len]
+            print('  hash:       {}'
+                  .format(binascii.hexlify(img_digest).decode('ascii')))
+            offs += digest_len
+            sig = self.inf[offs:offs + sig_len]
+            offs += sig_len
+
+            if img_type == SHDR_BOOTSTRAP_TA or img_type == SHDR_ENCRYPTED_TA:
+                display_ta()
+            elif img_type == SHDR_SUBKEY:
+                img_uuid = self.inf[offs:offs + UUID_SIZE]
+                img_subkey = self.inf[offs + UUID_SIZE:
+                                      offs + UUID_SIZE + SK_HDR_SIZE]
+                [name_size, subkey_version, max_depth, algo,
+                 attr_count] = struct.unpack('<IIIII', img_subkey)
+                if algo not in sig_tee_alg.values():
+                    raise Exception('Unrecognized algorithm: 0x{:08x}'
+                                    .format(algo))
+                algo_name = value_to_key(sig_tee_alg, algo)
+                print(' struct shdr_subkey')
+                print('  uuid:       {}'.format(uuid.UUID(bytes=img_uuid)))
+                print('  name_size:  {}'.format(name_size))
+                print('  subkey_version: {}'.format(subkey_version))
+                print('  max_depth:  {}'.format(max_depth))
+                print('  algo:       0x{:08x} ({})'.format(algo, algo_name))
+                print('  attr_count: {}'.format(attr_count))
+                offs += img_size
+            else:
+                raise Exception("Unsupported image type: {}".format(img_type))
 
     def decrypt_ta(enc_key):
         from cryptography.hazmat.primitives.ciphers.aead import AESGCM
@@ -565,13 +787,33 @@
         if self.ta_uuid != uuid.bytes:
             raise Exception('UUID does not match')
 
+    def add_subkey(self, subkey_file, name):
+        sk_image = BinaryImage(subkey_file, None)
+        self.subkey_img = sk_image.inf
+        sk_image.parse()
+        if not hasattr(sk_image, 'next_inf'):
+            logger.error('Invalid subkey file')
+            sys.exit(1)
+        while len(sk_image.next_inf) > 0:
+            sk_image = BinaryImage(sk_image.next_inf, None)
+            sk_image.parse()
+
+        if name is None:
+            name = ''
+        self.previous_max_depth = sk_image.max_depth
+        self.name_img = str.encode(name).ljust(sk_image.name_size, b'\0')
+
     def write(self, outf):
         with open(outf, 'wb') as f:
+            if hasattr(self, 'subkey_img'):
+                f.write(self.subkey_img)
+                f.write(self.name_img)
             f.write(self.shdr)
             f.write(self.img_digest)
             f.write(self.sig)
-            f.write(self.ta_uuid)
-            f.write(self.ta_version)
+            if hasattr(self, 'ta_uuid'):
+                f.write(self.ta_uuid)
+                f.write(self.ta_version)
             if hasattr(self, 'ehdr'):
                 f.write(self.ehdr)
                 f.write(self.nonce)
@@ -595,11 +837,24 @@
 
 def command_sign_enc(args):
     ta_image = load_ta_image(args)
+    if args.subkey:
+        ta_image.add_subkey(args.subkey, args.name)
     ta_image.sign()
     ta_image.write(args.outf)
     logger.info('Successfully signed application.')
 
 
+def command_sign_subkey(args):
+    image = BinaryImage(args.inf, args.key)
+    if args.subkey:
+        image.add_subkey(args.subkey, args.name)
+    image.set_subkey(args.algo, args.name, args.uuid, args.subkey_version,
+                     args.max_depth, args.name_size)
+    image.sign()
+    image.write(args.outf)
+    logger.info('Successfully signed subkey.')
+
+
 def command_digest(args):
     import base64
 
@@ -617,17 +872,53 @@
 
 
 def command_verify(args):
-    ta_image = BinaryImage(args.inf, args.key)
-    ta_image.parse()
-    if hasattr(ta_image, 'ciphertext'):
-        if args.enc_key is None:
-            logger.error('--enc_key needed to decrypt TA')
-            sys.exit(1)
-        ta_image.decrypt_ta(args.enc_key)
-    ta_image.verify_signature()
-    ta_image.verify_digest()
-    ta_image.verify_uuid(args.uuid)
-    logger.info('Trusted application is correctly verified.')
+    import uuid
+
+    image = BinaryImage(args.inf, args.key)
+    next_uuid = None
+    max_depth = -1
+    while True:
+        image.parse()
+        if hasattr(image, 'subkey_hdr'):  # Subkey
+            print('Subkey UUID: {}'.format(uuid.UUID(bytes=image.uuid)))
+            image.verify_signature()
+            image.verify_digest()
+            if next_uuid:
+                if uuid.UUID(bytes=image.uuid) != next_uuid:
+                    raise Exception('UUID {} does not match {}'
+                                    .format(uuid.UUID(bytes=image.uuid),
+                                            next_uuid))
+            if max_depth >= 0:
+                if image.max_depth < 0 or image.max_depth >= max_depth:
+                    raise Exception('Invalid max_depth {} not less than {}'
+                                    .format(image.max_depth, max_depth))
+            max_depth = image.max_depth
+            if len(image.next_inf) == 0:
+                logger.info('Subkey is correctly verified.')
+                return
+            if image.name_size > 0:
+                next_uuid = uuid_v5_sha512(image.uuid,
+                                           name_img_to_str(image.name_img))
+            else:
+                next_uuid = image.uuid
+            image = BinaryImage(image.next_inf, image.subkey_key)
+        else:  # TA
+            print('TA UUID: {}'.format(uuid.UUID(bytes=image.ta_uuid)))
+            if next_uuid:
+                if uuid.UUID(bytes=image.ta_uuid) != next_uuid:
+                    raise Exception('UUID {} does not match {}'
+                                    .format(uuid.UUID(bytes=image.ta_uuid),
+                                            next_uuid))
+            if hasattr(image, 'ciphertext'):
+                if args.enc_key is None:
+                    logger.error('--enc_key needed to decrypt TA')
+                    sys.exit(1)
+                image.decrypt_ta(args.enc_key)
+            image.verify_signature()
+            image.verify_digest()
+            image.verify_uuid(args.uuid)
+            logger.info('Trusted application is correctly verified.')
+            return
 
 
 def command_display(args):
@@ -635,6 +926,32 @@
     ta_image.display()
 
 
+def command_subkey_uuid(args):
+    import uuid
+
+    sk_image = BinaryImage(args.inf, None)
+    sk_image.parse()
+    if not hasattr(sk_image, 'next_inf'):
+        logger.error('Invalid subkey file')
+        sys.exit(1)
+    print('Subkey UUID: {}'.format(uuid.UUID(bytes=sk_image.uuid)))
+    while len(sk_image.next_inf) > 0:
+        sk_image = BinaryImage(sk_image.next_inf, None)
+        sk_image.parse()
+        print('Subkey UUID: {}'.format(uuid.UUID(bytes=sk_image.uuid)))
+    if args.name:
+        if len(args.name) > sk_image.name_size:
+            logger.error('Length of name ({}) '.format(len(args.name)) +
+                         'is larger than max name size ({})'
+                         .format(sk_image.name_size))
+            sys.exit(1)
+        print('Next subkey UUID: {}'
+              .format(uuid_v5_sha512(sk_image.uuid, args.name)))
+    else:
+        print('Next subkey UUID unchanged: {}'
+              .format(uuid.UUID(bytes=sk_image.uuid)))
+
+
 def main():
     import logging
     import os