xref: /optee_os/scripts/sign_encrypt.py (revision 9fc2442cc66c279cb962c90c4375746fc9b28bb9)
1#!/usr/bin/env python3
2# SPDX-License-Identifier: BSD-2-Clause
3#
4# Copyright (c) 2015, 2017, 2019, Linaro Limited
5#
6
7import sys
8
9
10algo = {'TEE_ALG_RSASSA_PKCS1_PSS_MGF1_SHA256': 0x70414930,
11        'TEE_ALG_RSASSA_PKCS1_V1_5_SHA256': 0x70004830}
12
13
14def uuid_parse(s):
15    from uuid import UUID
16    return UUID(s)
17
18
19def int_parse(str):
20    return int(str, 0)
21
22
23def get_args(logger):
24    from argparse import ArgumentParser, RawDescriptionHelpFormatter
25    import textwrap
26    command_base = ['sign-enc', 'digest', 'stitch']
27    command_aliases_digest = ['generate-digest']
28    command_aliases_stitch = ['stitch-ta']
29    command_aliases = command_aliases_digest + command_aliases_stitch
30    command_choices = command_base + command_aliases
31
32    dat = '[' + ', '.join(command_aliases_digest) + ']'
33    sat = '[' + ', '.join(command_aliases_stitch) + ']'
34
35    parser = ArgumentParser(
36        description='Sign and encrypt (optional) a Tusted Application for' +
37        ' OP-TEE.',
38        usage='\n   %(prog)s command [ arguments ]\n\n'
39
40        '   command:\n' +
41        '     sign-enc    Generate signed and optionally encrypted loadable' +
42        ' TA image file.\n' +
43        '                 Takes arguments --uuid, --ta-version, --in, --out,' +
44        ' --key\n' +
45        '                 and --enc-key (optional).\n' +
46        '     digest      Generate loadable TA binary image digest' +
47        ' for offline\n' +
48        '                 signing. Takes arguments --uuid, --ta-version,' +
49        ' --in, --key,\n'
50        '                 --enc-key (optional), --algo (optional) and' +
51        ' --dig.\n' +
52        '     stitch      Generate loadable signed and encrypted TA binary' +
53        ' image file from\n' +
54        '                 TA raw image and its signature. Takes' +
55        ' arguments\n' +
56        '                 --uuid, --in, --key, --enc-key (optional), --out,' +
57        ' --algo (optional) and --sig.\n\n' +
58        '   %(prog)s --help  show available commands and arguments\n\n',
59        formatter_class=RawDescriptionHelpFormatter,
60        epilog=textwrap.dedent('''\
61            If no command is given, the script will default to "sign-enc".
62
63            command aliases:
64              The command \'digest\' can be aliased by ''' + dat + '''
65              The command \'stitch\' can be aliased by ''' + sat + '\n' + '''
66            example offline signing command using OpenSSL for algorithm
67            TEE_ALG_RSASSA_PKCS1_PSS_MGF1_SHA256:
68              base64 -d <UUID>.dig | \\
69              openssl pkeyutl -sign -inkey <KEYFILE>.pem \\
70                  -pkeyopt digest:sha256 -pkeyopt rsa_padding_mode:pss \\
71                  -pkeyopt rsa_pss_saltlen:digest \\
72                  -pkeyopt rsa_mgf1_md:sha256 | \\
73              base64 > <UUID>.sig\n
74            example offline signing command using OpenSSL for algorithm
75            TEE_ALG_RSASSA_PKCS1_V1_5_SHA256:
76              base64 -d <UUID>.dig | \\
77              openssl pkeyutl -sign -inkey <KEYFILE>.pem \\
78                  -pkeyopt digest:sha256 -pkeyopt rsa_padding_mode:pkcs1 | \\
79              base64 > <UUID>.sig
80            '''))
81
82    parser.add_argument(
83        'command', choices=command_choices, nargs='?',
84        default='sign-enc',
85        help='Command, one of [' + ', '.join(command_base) + ']')
86    parser.add_argument('--uuid', required=True,
87                        type=uuid_parse, help='String UUID of the TA')
88    parser.add_argument('--key', required=True,
89                        help='Name of signing key file (PEM format)')
90    parser.add_argument('--enc-key', required=False,
91                        help='Encryption key string')
92    parser.add_argument(
93        '--ta-version', required=False, type=int_parse, default=0,
94        help='TA version stored as a 32-bit unsigned integer and used for\n' +
95        'rollback protection of TA install in the secure database.\n' +
96        'Defaults to 0.')
97    parser.add_argument(
98        '--sig', required=False, dest='sigf',
99        help='Name of signature input file, defaults to <UUID>.sig')
100    parser.add_argument(
101        '--dig', required=False, dest='digf',
102        help='Name of digest output file, defaults to <UUID>.dig')
103    parser.add_argument(
104        '--in', required=True, dest='inf',
105        help='Name of application input file, defaults to <UUID>.stripped.elf')
106    parser.add_argument(
107        '--out', required=False, dest='outf',
108        help='Name of application output file, defaults to <UUID>.ta')
109    parser.add_argument('--algo', required=False, choices=list(algo.keys()),
110                        default='TEE_ALG_RSASSA_PKCS1_PSS_MGF1_SHA256',
111                        help='The hash and signature algorithm, ' +
112                        'defaults to TEE_ALG_RSASSA_PKCS1_PSS_MGF1_SHA256. ' +
113                        'Allowed values are: ' +
114                        ', '.join(list(algo.keys())), metavar='')
115
116    parsed = parser.parse_args()
117
118    # Check parameter combinations
119
120    if parsed.digf is None and \
121       parsed.outf is not None and \
122       parsed.command in ['digest'] + command_aliases_digest:
123        logger.error('A digest was requested, but argument --out was given.' +
124                     '  Did you mean:\n  ' +
125                     parser.prog+' --dig ' + parsed.outf + ' ...')
126        sys.exit(1)
127
128    if parsed.digf is not None \
129       and parsed.outf is not None \
130       and parsed.command in ['digest'] + command_aliases_digest:
131        logger.warn('A digest was requested, but arguments --dig and ' +
132                    '--out were given.\n' +
133                    '  --out will be ignored.')
134
135    # Set defaults for optional arguments.
136
137    if parsed.sigf is None:
138        parsed.sigf = str(parsed.uuid)+'.sig'
139    if parsed.digf is None:
140        parsed.digf = str(parsed.uuid)+'.dig'
141    if parsed.inf is None:
142        parsed.inf = str(parsed.uuid)+'.stripped.elf'
143    if parsed.outf is None:
144        parsed.outf = str(parsed.uuid)+'.ta'
145
146    return parsed
147
148
149def main():
150    try:
151        from Cryptodome.Signature import pss
152        from Cryptodome.Signature import pkcs1_15
153        from Cryptodome.Hash import SHA256
154        from Cryptodome.PublicKey import RSA
155    except ImportError:
156        from Crypto.Signature import pss
157        from Crypto.Signature import pkcs1_15
158        from Crypto.Hash import SHA256
159        from Crypto.PublicKey import RSA
160    import base64
161    import logging
162    import os
163    import struct
164
165    logging.basicConfig()
166    logger = logging.getLogger(os.path.basename(__file__))
167
168    args = get_args(logger)
169
170    with open(args.key, 'rb') as f:
171        key = RSA.importKey(f.read())
172
173    with open(args.inf, 'rb') as f:
174        img = f.read()
175
176    h = SHA256.new()
177
178    digest_len = h.digest_size
179    sig_len = key.size_in_bytes()
180
181    img_size = len(img)
182
183    hdr_version = args.ta_version  # struct shdr_bootstrap_ta::ta_version
184
185    magic = 0x4f545348   # SHDR_MAGIC
186    if args.enc_key:
187        img_type = 2         # SHDR_ENCRYPTED_TA
188    else:
189        img_type = 1         # SHDR_BOOTSTRAP_TA
190
191    shdr = struct.pack('<IIIIHH',
192                       magic, img_type, img_size, algo[args.algo],
193                       digest_len, sig_len)
194    shdr_uuid = args.uuid.bytes
195    shdr_version = struct.pack('<I', hdr_version)
196
197    if args.enc_key:
198        from Cryptodome.Cipher import AES
199        cipher = AES.new(bytearray.fromhex(args.enc_key), AES.MODE_GCM)
200        ciphertext, tag = cipher.encrypt_and_digest(img)
201
202        enc_algo = 0x40000810  # TEE_ALG_AES_GCM
203        flags = 0              # SHDR_ENC_KEY_DEV_SPECIFIC
204        ehdr = struct.pack('<IIHH',
205                           enc_algo, flags, len(cipher.nonce), len(tag))
206
207    h.update(shdr)
208    h.update(shdr_uuid)
209    h.update(shdr_version)
210    if args.enc_key:
211        h.update(ehdr)
212        h.update(cipher.nonce)
213        h.update(tag)
214    h.update(img)
215    img_digest = h.digest()
216
217    def write_image_with_signature(sig):
218        with open(args.outf, 'wb') as f:
219            f.write(shdr)
220            f.write(img_digest)
221            f.write(sig)
222            f.write(shdr_uuid)
223            f.write(shdr_version)
224            if args.enc_key:
225                f.write(ehdr)
226                f.write(cipher.nonce)
227                f.write(tag)
228                f.write(ciphertext)
229            else:
230                f.write(img)
231
232    def sign_encrypt_ta():
233        if not key.has_private():
234            logger.error('Provided key cannot be used for signing, ' +
235                         'please use offline-signing mode.')
236            sys.exit(1)
237        else:
238            signer = pss.new(key)
239            sig = signer.sign(h)
240            if len(sig) != sig_len:
241                raise Exception(("Actual signature length is not equal to ",
242                                 "the computed one: {} != {}").
243                                format(len(sig), sig_len))
244            write_image_with_signature(sig)
245            logger.info('Successfully signed application.')
246
247    def generate_digest():
248        with open(args.digf, 'wb+') as digfile:
249            digfile.write(base64.b64encode(img_digest))
250
251    def stitch_ta():
252        try:
253            with open(args.sigf, 'r') as sigfile:
254                sig = base64.b64decode(sigfile.read())
255        except IOError:
256            if not os.path.exists(args.digf):
257                generate_digest()
258            logger.error('No signature file found. Please sign\n %s\n' +
259                         'offline and place the signature at \n %s\n' +
260                         'or pass a different location ' +
261                         'using the --sig argument.\n',
262                         args.digf, args.sigf)
263            sys.exit(1)
264        else:
265            if args.algo == 'TEE_ALG_RSASSA_PKCS1_PSS_MGF1_SHA256':
266                verifier = pss.new(key)
267            elif args.algo == 'TEE_ALG_RSASSA_PKCS1_V1_5_SHA256':
268                verifier = pkcs1_15.new(key)
269            try:
270                verifier.verify(h, sig)
271                write_image_with_signature(sig)
272                logger.info('Successfully applied signature.')
273            except ValueError:
274                logger.error('Verification failed, ignoring given signature.')
275                sys.exit(1)
276
277    # dispatch command
278    {
279        'sign-enc': sign_encrypt_ta,
280        'digest': generate_digest,
281        'generate-digest': generate_digest,
282        'stitch': stitch_ta,
283        'stitch-ta': stitch_ta
284    }.get(args.command, 'sign_encrypt_ta')()
285
286
287if __name__ == "__main__":
288    main()
289