Jens Wiklander | bc42074 | 2015-05-05 14:59:15 +0200 | [diff] [blame] | 1 | #!/usr/bin/env python |
| 2 | # |
Jens Wiklander | cd5cf43 | 2017-11-28 16:59:15 +0100 | [diff] [blame] | 3 | # Copyright (c) 2015, 2017, Linaro Limited |
Jens Wiklander | bc42074 | 2015-05-05 14:59:15 +0200 | [diff] [blame] | 4 | # |
Jens Wiklander | cd5cf43 | 2017-11-28 16:59:15 +0100 | [diff] [blame] | 5 | # SPDX-License-Identifier: BSD-2-Clause |
Jens Wiklander | cd5cf43 | 2017-11-28 16:59:15 +0100 | [diff] [blame] | 6 | |
Markus S. Wamser | 1cdd95a | 2019-04-30 12:03:12 +0200 | [diff] [blame^] | 7 | import sys |
| 8 | |
Jerome Forissier | 4a47792 | 2018-11-14 11:02:49 +0100 | [diff] [blame] | 9 | |
Jens Wiklander | cd5cf43 | 2017-11-28 16:59:15 +0100 | [diff] [blame] | 10 | def uuid_parse(s): |
| 11 | from uuid import UUID |
| 12 | return UUID(s) |
| 13 | |
| 14 | |
| 15 | def int_parse(str): |
| 16 | return int(str, 0) |
| 17 | |
Jens Wiklander | bc42074 | 2015-05-05 14:59:15 +0200 | [diff] [blame] | 18 | |
Markus S. Wamser | 1cdd95a | 2019-04-30 12:03:12 +0200 | [diff] [blame^] | 19 | def get_args(logger): |
| 20 | from argparse import ArgumentParser, RawDescriptionHelpFormatter |
| 21 | import textwrap |
| 22 | command_base = ['sign', 'digest', 'stitch'] |
| 23 | command_aliases_digest = ['generate-digest'] |
| 24 | command_aliases_stitch = ['stitch-ta'] |
| 25 | command_aliases = command_aliases_digest + command_aliases_stitch |
| 26 | command_choices = command_base + command_aliases |
Jens Wiklander | bc42074 | 2015-05-05 14:59:15 +0200 | [diff] [blame] | 27 | |
Markus S. Wamser | 1cdd95a | 2019-04-30 12:03:12 +0200 | [diff] [blame^] | 28 | dat = '[' + ', '.join(command_aliases_digest) + ']' |
| 29 | sat = '[' + ', '.join(command_aliases_stitch) + ']' |
| 30 | |
| 31 | parser = ArgumentParser( |
| 32 | description='Sign a Tusted Application for OP-TEE.', |
| 33 | usage='\n %(prog)s command [ arguments ]\n\n' |
| 34 | |
| 35 | ' command:\n' + |
| 36 | ' sign Generate signed loadable TA image file.\n' + |
| 37 | ' Takes arguments --uuid, --in, --out' + |
| 38 | ' and --key.\n' + |
| 39 | ' digest Generate loadable TA binary image digest' + |
| 40 | ' for offline\n' + |
| 41 | ' signing. Takes arguments --uuid, --in and' + |
| 42 | ' --dig.\n' + |
| 43 | ' stitch Generate loadable signed TA binary image' + |
| 44 | ' file from\n' + |
| 45 | ' TA raw image and its signature. Takes' + |
| 46 | ' arguments\n' + |
| 47 | ' --uuid, --in, --out, and --sig.\n\n' + |
| 48 | ' %(prog)s --help show available commands and arguments\n\n', |
| 49 | formatter_class=RawDescriptionHelpFormatter, |
| 50 | epilog=textwrap.dedent('''\ |
| 51 | If no command is given, the script will default to "sign". |
| 52 | |
| 53 | command aliases: |
| 54 | The command \'digest\' can be aliased by ''' + dat + ''' |
| 55 | The command \'stitch\' can be aliased by ''' + sat + '\n' + ''' |
| 56 | example offline signing command using OpenSSL: |
| 57 | base64 -d <UUID>.dig | \\ |
| 58 | openssl pkeyutl -sign -inkey <KEYFILE>.pem \\ |
| 59 | -pkeyopt digest:sha256 \\ |
| 60 | -pkeyopt rsa_padding_mode:pkcs1 | \\ |
| 61 | base64 > <UUID>.sig |
| 62 | ''')) |
| 63 | |
| 64 | parser.add_argument( |
| 65 | 'command', choices=command_choices, nargs='?', |
| 66 | default='sign', |
| 67 | help='Command, one of [' + ', '.join(command_base) + ']') |
Jens Wiklander | cd5cf43 | 2017-11-28 16:59:15 +0100 | [diff] [blame] | 68 | parser.add_argument('--uuid', required=True, |
Markus S. Wamser | 1cdd95a | 2019-04-30 12:03:12 +0200 | [diff] [blame^] | 69 | type=uuid_parse, help='String UUID of the TA') |
| 70 | parser.add_argument('--key', required=True, |
| 71 | help='Name of key file (PEM format)') |
| 72 | parser.add_argument( |
| 73 | '--sig', required=False, dest='sigf', |
| 74 | help='Name of signature input file, defaults to <UUID>.sig') |
| 75 | parser.add_argument( |
| 76 | '--dig', required=False, dest='digf', |
| 77 | help='Name of digest output file, defaults to <UUID>.dig') |
| 78 | parser.add_argument( |
| 79 | '--in', required=False, dest='inf', |
| 80 | help='Name of application input file, defaults to <UUID>.stripped.elf') |
| 81 | parser.add_argument( |
| 82 | '--out', required=False, dest='outf', |
| 83 | help='Name of application output file, defaults to <UUID>.ta') |
| 84 | |
| 85 | parsed = parser.parse_args() |
| 86 | |
| 87 | # Check parameter combinations |
| 88 | |
| 89 | if parsed.digf is None and \ |
| 90 | parsed.outf is not None and \ |
| 91 | parsed.command in ['digest'] + command_aliases_digest: |
| 92 | logger.error('A digest was requested, but argument --out was given.' + |
| 93 | ' Did you mean:\n ' + |
| 94 | parser.prog+' --dig ' + parsed.outf + ' ...') |
| 95 | sys.exit(1) |
| 96 | |
| 97 | if parsed.digf is not None \ |
| 98 | and parsed.outf is not None \ |
| 99 | and parsed.command in ['digest'] + command_aliases_digest: |
| 100 | logger.warn('A digest was requested, but arguments --dig and ' + |
| 101 | '--out were given.\n' + |
| 102 | ' --out will be ignored.') |
| 103 | |
| 104 | # Set defaults for optional arguments. |
| 105 | |
| 106 | if parsed.sigf is None: |
| 107 | parsed.sigf = str(parsed.uuid)+'.sig' |
| 108 | if parsed.digf is None: |
| 109 | parsed.digf = str(parsed.uuid)+'.dig' |
| 110 | if parsed.inf is None: |
| 111 | parsed.inf = str(parsed.uuid)+'.stripped.elf' |
| 112 | if parsed.outf is None: |
| 113 | parsed.outf = str(parsed.uuid)+'.ta' |
| 114 | |
| 115 | return parsed |
Jens Wiklander | cd5cf43 | 2017-11-28 16:59:15 +0100 | [diff] [blame] | 116 | |
Jens Wiklander | bc42074 | 2015-05-05 14:59:15 +0200 | [diff] [blame] | 117 | |
| 118 | def main(): |
Jens Wiklander | cd5cf43 | 2017-11-28 16:59:15 +0100 | [diff] [blame] | 119 | from Crypto.Signature import PKCS1_v1_5 |
| 120 | from Crypto.Hash import SHA256 |
| 121 | from Crypto.PublicKey import RSA |
Markus S. Wamser | 1cdd95a | 2019-04-30 12:03:12 +0200 | [diff] [blame^] | 122 | from Crypto.Util.number import ceil_div |
| 123 | import base64 |
| 124 | import logging |
| 125 | import os |
Jens Wiklander | cd5cf43 | 2017-11-28 16:59:15 +0100 | [diff] [blame] | 126 | import struct |
Jens Wiklander | bc42074 | 2015-05-05 14:59:15 +0200 | [diff] [blame] | 127 | |
Markus S. Wamser | 1cdd95a | 2019-04-30 12:03:12 +0200 | [diff] [blame^] | 128 | logging.basicConfig() |
| 129 | logger = logging.getLogger(os.path.basename(__file__)) |
Jens Wiklander | bc42074 | 2015-05-05 14:59:15 +0200 | [diff] [blame] | 130 | |
Markus S. Wamser | 1cdd95a | 2019-04-30 12:03:12 +0200 | [diff] [blame^] | 131 | args = get_args(logger) |
Jens Wiklander | bc42074 | 2015-05-05 14:59:15 +0200 | [diff] [blame] | 132 | |
Markus S. Wamser | 1cdd95a | 2019-04-30 12:03:12 +0200 | [diff] [blame^] | 133 | with open(args.key, 'rb') as f: |
| 134 | key = RSA.importKey(f.read()) |
Jens Wiklander | bc42074 | 2015-05-05 14:59:15 +0200 | [diff] [blame] | 135 | |
Markus S. Wamser | 1cdd95a | 2019-04-30 12:03:12 +0200 | [diff] [blame^] | 136 | with open(args.inf, 'rb') as f: |
| 137 | img = f.read() |
| 138 | |
Jens Wiklander | cd5cf43 | 2017-11-28 16:59:15 +0100 | [diff] [blame] | 139 | h = SHA256.new() |
Jens Wiklander | bc42074 | 2015-05-05 14:59:15 +0200 | [diff] [blame] | 140 | |
Jens Wiklander | cd5cf43 | 2017-11-28 16:59:15 +0100 | [diff] [blame] | 141 | digest_len = h.digest_size |
Markus S. Wamser | 1cdd95a | 2019-04-30 12:03:12 +0200 | [diff] [blame^] | 142 | sig_len = ceil_div(key.size() + 1, 8) |
Jens Wiklander | cd5cf43 | 2017-11-28 16:59:15 +0100 | [diff] [blame] | 143 | img_size = len(img) |
Jens Wiklander | bc42074 | 2015-05-05 14:59:15 +0200 | [diff] [blame] | 144 | |
Markus S. Wamser | 1cdd95a | 2019-04-30 12:03:12 +0200 | [diff] [blame^] | 145 | hdr_version = 0 # SHDR_VERSION (always 0) |
| 146 | magic = 0x4f545348 # SHDR_MAGIC |
| 147 | img_type = 1 # SHDR_BOOTSTRAP_TA |
Jens Wiklander | cd5cf43 | 2017-11-28 16:59:15 +0100 | [diff] [blame] | 148 | algo = 0x70004830 # TEE_ALG_RSASSA_PKCS1_V1_5_SHA256 |
| 149 | shdr = struct.pack('<IIIIHH', |
| 150 | magic, img_type, img_size, algo, digest_len, sig_len) |
| 151 | shdr_uuid = args.uuid.bytes |
Markus S. Wamser | 1cdd95a | 2019-04-30 12:03:12 +0200 | [diff] [blame^] | 152 | shdr_version = struct.pack('<I', hdr_version) |
Jens Wiklander | bc42074 | 2015-05-05 14:59:15 +0200 | [diff] [blame] | 153 | |
Jens Wiklander | cd5cf43 | 2017-11-28 16:59:15 +0100 | [diff] [blame] | 154 | h.update(shdr) |
| 155 | h.update(shdr_uuid) |
| 156 | h.update(shdr_version) |
| 157 | h.update(img) |
Markus S. Wamser | 1cdd95a | 2019-04-30 12:03:12 +0200 | [diff] [blame^] | 158 | img_digest = h.digest() |
Jens Wiklander | bc42074 | 2015-05-05 14:59:15 +0200 | [diff] [blame] | 159 | |
Markus S. Wamser | 1cdd95a | 2019-04-30 12:03:12 +0200 | [diff] [blame^] | 160 | def write_image_with_signature(sig): |
| 161 | with open(args.outf, 'wb') as f: |
| 162 | f.write(shdr) |
| 163 | f.write(img_digest) |
| 164 | f.write(sig) |
| 165 | f.write(shdr_uuid) |
| 166 | f.write(shdr_version) |
| 167 | f.write(img) |
| 168 | |
| 169 | def sign_ta(): |
| 170 | if not key.has_private(): |
| 171 | logger.error('Provided key cannot be used for signing, ' + |
| 172 | 'please use offline-signing mode.') |
| 173 | else: |
| 174 | signer = PKCS1_v1_5.new(key) |
| 175 | sig = signer.sign(h) |
| 176 | write_image_with_signature(sig) |
| 177 | logger.info('Successfully signed application.') |
| 178 | |
| 179 | def generate_digest(): |
| 180 | with open(args.digf, 'wb+') as digfile: |
| 181 | digfile.write(base64.b64encode(img_digest)) |
| 182 | |
| 183 | def stitch_ta(): |
| 184 | try: |
| 185 | with open(args.sigf, 'r') as sigfile: |
| 186 | sig = base64.b64decode(sigfile.read()) |
| 187 | except IOError: |
| 188 | if not os.path.exits(args.digf): |
| 189 | generate_digest() |
| 190 | logger.error('No signature file found. Please sign\n %s\n' + |
| 191 | 'offline and place the signature at \n %s\n' + |
| 192 | 'or pass a different location ' + |
| 193 | 'using the --sig argument.\n', |
| 194 | args.digf, args.sigf) |
| 195 | else: |
| 196 | verifier = PKCS1_v1_5.new(key) |
| 197 | if verifier.verify(h, sig): |
| 198 | write_image_with_signature(sig) |
| 199 | logger.info('Successfully applied signature.') |
| 200 | else: |
| 201 | logger.error('Verification failed, ignoring given signature.') |
| 202 | |
| 203 | command_dict = { |
| 204 | 'sign': sign_ta, |
| 205 | 'digest': generate_digest, |
| 206 | 'generate-digest': generate_digest, |
| 207 | 'stitch': stitch_ta, |
| 208 | 'stitch-ta': stitch_ta |
| 209 | } |
| 210 | |
| 211 | fun = command_dict.get(args.command, 'sign_ta') |
| 212 | fun() |
Jens Wiklander | cd5cf43 | 2017-11-28 16:59:15 +0100 | [diff] [blame] | 213 | |
Jens Wiklander | bc42074 | 2015-05-05 14:59:15 +0200 | [diff] [blame] | 214 | |
| 215 | if __name__ == "__main__": |
Jens Wiklander | cd5cf43 | 2017-11-28 16:59:15 +0100 | [diff] [blame] | 216 | main() |