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