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