xref: /optee_os/scripts/sign_encrypt.py (revision f182afc4d36c3c03a481a6c772424bf7028d3019)
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
14enc_key_type = {'SHDR_ENC_KEY_DEV_SPECIFIC': 0x0,
15                'SHDR_ENC_KEY_CLASS_WIDE': 0x1}
16
17SHDR_BOOTSTRAP_TA = 1
18SHDR_ENCRYPTED_TA = 2
19SHDR_MAGIC = 0x4f545348
20SHDR_SIZE = 20
21
22
23def uuid_parse(s):
24    from uuid import UUID
25    return UUID(s)
26
27
28def int_parse(str):
29    return int(str, 0)
30
31
32def get_args():
33    def arg_add_uuid(parser):
34        parser.add_argument(
35            '--uuid', required=True, type=uuid_parse,
36            help='String UUID of the TA')
37
38    def arg_add_key(parser):
39        parser.add_argument(
40            '--key', required=True, help='''
41                Name of signing and verification key file (PEM format) or an
42                Amazon Resource Name (arn:) of an AWS KMS asymmetric key.
43                At least public key for the commands digest, stitch, and
44                verify, else a private key''')
45
46    def arg_add_enc_key(parser):
47        parser.add_argument(
48            '--enc-key', required=False, help='Encryption key string')
49
50    def arg_add_enc_key_type(parser):
51        parser.add_argument(
52            '--enc-key-type', required=False,
53            default='SHDR_ENC_KEY_DEV_SPECIFIC',
54            choices=list(enc_key_type.keys()), help='''
55                Encryption key type,
56                Defaults to SHDR_ENC_KEY_DEV_SPECIFIC.''')
57
58    def arg_add_ta_version(parser):
59        parser.add_argument(
60            '--ta-version', required=False, type=int_parse, default=0, help='''
61                TA version stored as a 32-bit unsigned integer and used for
62                rollback protection of TA install in the secure database.
63                Defaults to 0.''')
64
65    def arg_add_sig(parser):
66        parser.add_argument(
67            '--sig', required=True, dest='sigf',
68            help='Name of signature input file, defaults to <UUID>.sig')
69
70    def arg_add_dig(parser):
71        parser.add_argument(
72            '--dig', required=True, dest='digf',
73            help='Name of digest output file, defaults to <UUID>.dig')
74
75    def arg_add_in(parser):
76        parser.add_argument(
77            '--in', required=False, dest='inf', help='''
78                Name of application input file, defaults to
79                <UUID>.stripped.elf''')
80
81    def arg_add_out(parser):
82        parser.add_argument(
83            '--out', required=True, dest='outf',
84            help='Name of application output file, defaults to <UUID>.ta')
85
86    def arg_add_algo(parser):
87        parser.add_argument(
88            '--algo', required=False, choices=list(algo.keys()),
89            default='TEE_ALG_RSASSA_PKCS1_PSS_MGF1_SHA256', help='''
90                The hash and signature algorithm.
91                Defaults to TEE_ALG_RSASSA_PKCS1_PSS_MGF1_SHA256.''')
92
93    def get_outf_default(parsed):
94        return str(parsed.uuid) + '.ta'
95
96    def get_inf_default(parsed):
97        return str(parsed.uuid) + '.stripped.elf'
98
99    def get_sigf_default(parsed):
100        return str(parsed.uuid) + '.sig'
101
102    def get_digf_default(parsed):
103        return str(parsed.uuid) + '.dig'
104
105    def assign_default_value(parsed, attr, func):
106        if hasattr(parsed, attr) and getattr(parsed, attr) is None:
107            setattr(parsed, attr, func(parsed))
108
109    import argparse
110    import textwrap
111
112    parser = argparse.ArgumentParser(
113        description='Sign and encrypt (optional) a Trusted Application ' +
114        ' for OP-TEE.',
115        usage='%(prog)s <command> ...',
116        epilog='<command> -h for detailed help')
117    subparsers = parser.add_subparsers(
118            title='valid commands, with possible aliases in ()',
119            dest='command', metavar='')
120
121    parser_sign_enc = subparsers.add_parser(
122        'sign-enc', prog=parser.prog + ' sign-enc',
123        help='Generate signed and optionally encrypted loadable TA image file')
124    arg_add_uuid(parser_sign_enc)
125    arg_add_ta_version(parser_sign_enc)
126    arg_add_in(parser_sign_enc)
127    arg_add_out(parser_sign_enc)
128    arg_add_key(parser_sign_enc)
129    arg_add_enc_key(parser_sign_enc)
130    arg_add_enc_key_type(parser_sign_enc)
131    arg_add_algo(parser_sign_enc)
132
133    parser_digest = subparsers.add_parser(
134        'digest', aliases=['generate-digest'], prog=parser.prog + ' digest',
135        formatter_class=argparse.RawDescriptionHelpFormatter,
136        help='Generate loadable TA binary image digest for offline signing',
137        epilog=textwrap.dedent('''\
138            example offline signing command using OpenSSL for algorithm
139            TEE_ALG_RSASSA_PKCS1_PSS_MGF1_SHA256:
140              base64 -d <UUID>.dig | \\
141              openssl pkeyutl -sign -inkey <KEYFILE>.pem \\
142                  -pkeyopt digest:sha256 -pkeyopt rsa_padding_mode:pss \\
143                  -pkeyopt rsa_pss_saltlen:digest \\
144                  -pkeyopt rsa_mgf1_md:sha256 | \\
145              base64 > <UUID>.sig
146
147            example offline signing command using OpenSSL for algorithm
148            TEE_ALG_RSASSA_PKCS1_V1_5_SHA256:
149              base64 -d <UUID>.dig | \\
150              openssl pkeyutl -sign -inkey <KEYFILE>.pem \\
151                  -pkeyopt digest:sha256 -pkeyopt rsa_padding_mode:pkcs1 | \\
152              base64 > <UUID>.sig
153            '''))
154    arg_add_uuid(parser_digest)
155    arg_add_ta_version(parser_digest)
156    arg_add_in(parser_digest)
157    arg_add_key(parser_digest)
158    arg_add_enc_key(parser_digest)
159    arg_add_enc_key_type(parser_digest)
160    arg_add_algo(parser_digest)
161    arg_add_dig(parser_digest)
162
163    parser_stitch = subparsers.add_parser(
164        'stitch', aliases=['stitch-ta'], prog=parser.prog + ' stich',
165        help='Generate loadable signed and encrypted TA binary image file' +
166        ' from TA raw image and its signature')
167    arg_add_uuid(parser_stitch)
168    arg_add_ta_version(parser_stitch)
169    arg_add_in(parser_stitch)
170    arg_add_key(parser_stitch)
171    arg_add_out(parser_stitch)
172    arg_add_enc_key(parser_stitch)
173    arg_add_enc_key_type(parser_stitch)
174    arg_add_algo(parser_stitch)
175    arg_add_sig(parser_stitch)
176
177    parser_verify = subparsers.add_parser(
178        'verify', prog=parser.prog + ' verify',
179        help='Verify signed TA binary')
180    arg_add_uuid(parser_verify)
181    arg_add_in(parser_verify)
182    arg_add_key(parser_verify)
183
184    argv = sys.argv[1:]
185    if (len(argv) > 0 and argv[0][0] == '-' and
186            argv[0] != '-h' and argv[0] != '--help'):
187        # The default sub-command is 'sign-enc' so add it to the parser
188        # if one is missing
189        argv = ['sign-enc'] + argv
190
191    parsed = parser.parse_args(argv)
192
193    if parsed.command is None:
194        parser.print_help()
195        sys.exit(1)
196
197    # Set a few defaults if defined for the current command
198    assign_default_value(parsed, 'inf', get_inf_default)
199    assign_default_value(parsed, 'outf', get_outf_default)
200    assign_default_value(parsed, 'sigf', get_sigf_default)
201    assign_default_value(parsed, 'digf', get_digf_default)
202
203    return parsed
204
205
206def main():
207    from cryptography import exceptions
208    from cryptography.hazmat.backends import default_backend
209    from cryptography.hazmat.primitives import serialization
210    from cryptography.hazmat.primitives import hashes
211    from cryptography.hazmat.primitives.asymmetric import padding
212    from cryptography.hazmat.primitives.asymmetric import rsa
213    from cryptography.hazmat.primitives.asymmetric import utils
214    import base64
215    import logging
216    import os
217    import struct
218
219    logging.basicConfig()
220    global logger
221    logger = logging.getLogger(os.path.basename(__file__))
222
223    args = get_args()
224
225    if args.key.startswith('arn:'):
226        from sign_helper_kms import _RSAPrivateKeyInKMS
227        key = _RSAPrivateKeyInKMS(args.key)
228    else:
229        with open(args.key, 'rb') as f:
230            data = f.read()
231
232            try:
233                key = serialization.load_pem_private_key(
234                          data,
235                          password=None,
236                          backend=default_backend())
237            except ValueError:
238                key = serialization.load_pem_public_key(
239                          data,
240                          backend=default_backend())
241
242    with open(args.inf, 'rb') as f:
243        img = f.read()
244
245    chosen_hash = hashes.SHA256()
246    h = hashes.Hash(chosen_hash, default_backend())
247
248    digest_len = chosen_hash.digest_size
249    sig_len = math.ceil(key.key_size / 8)
250
251    img_size = len(img)
252
253    hdr_version = args.ta_version  # struct shdr_bootstrap_ta::ta_version
254
255    magic = SHDR_MAGIC
256    if args.enc_key:
257        img_type = SHDR_ENCRYPTED_TA
258    else:
259        img_type = SHDR_BOOTSTRAP_TA
260
261    shdr = struct.pack('<IIIIHH',
262                       magic, img_type, img_size, algo[args.algo],
263                       digest_len, sig_len)
264    shdr_uuid = args.uuid.bytes
265    shdr_version = struct.pack('<I', hdr_version)
266
267    if args.enc_key:
268        from cryptography.hazmat.primitives.ciphers.aead import AESGCM
269        cipher = AESGCM(bytes.fromhex(args.enc_key))
270        # Use 12 bytes for nonce per recommendation
271        nonce = os.urandom(12)
272        out = cipher.encrypt(nonce, img, None)
273        ciphertext = out[:-16]
274        # Authentication Tag is always the last 16 bytes
275        tag = out[-16:]
276
277        enc_algo = 0x40000810      # TEE_ALG_AES_GCM
278        flags = enc_key_type[args.enc_key_type]
279        ehdr = struct.pack('<IIHH',
280                           enc_algo, flags, len(nonce), len(tag))
281
282    h.update(shdr)
283    h.update(shdr_uuid)
284    h.update(shdr_version)
285    if args.enc_key:
286        h.update(ehdr)
287        h.update(nonce)
288        h.update(tag)
289    h.update(img)
290    img_digest = h.finalize()
291
292    def write_image_with_signature(sig):
293        with open(args.outf, 'wb') as f:
294            f.write(shdr)
295            f.write(img_digest)
296            f.write(sig)
297            f.write(shdr_uuid)
298            f.write(shdr_version)
299            if args.enc_key:
300                f.write(ehdr)
301                f.write(nonce)
302                f.write(tag)
303                f.write(ciphertext)
304            else:
305                f.write(img)
306
307    def sign_encrypt_ta():
308        if not isinstance(key, rsa.RSAPrivateKey):
309            logger.error('Provided key cannot be used for signing, ' +
310                         'please use offline-signing mode.')
311            sys.exit(1)
312        else:
313            if args.algo == 'TEE_ALG_RSASSA_PKCS1_PSS_MGF1_SHA256':
314                sig = key.sign(
315                    img_digest,
316                    padding.PSS(
317                        mgf=padding.MGF1(chosen_hash),
318                        salt_length=digest_len
319                    ),
320                    utils.Prehashed(chosen_hash)
321                )
322            elif args.algo == 'TEE_ALG_RSASSA_PKCS1_V1_5_SHA256':
323                sig = key.sign(
324                    img_digest,
325                    padding.PKCS1v15(),
326                    utils.Prehashed(chosen_hash)
327                )
328
329            if len(sig) != sig_len:
330                raise Exception(("Actual signature length is not equal to ",
331                                 "the computed one: {} != {}").
332                                format(len(sig), sig_len))
333            write_image_with_signature(sig)
334            logger.info('Successfully signed application.')
335
336    def generate_digest():
337        with open(args.digf, 'wb+') as digfile:
338            digfile.write(base64.b64encode(img_digest))
339
340    def stitch_ta():
341        try:
342            with open(args.sigf, 'r') as sigfile:
343                sig = base64.b64decode(sigfile.read())
344        except IOError:
345            if not os.path.exists(args.digf):
346                generate_digest()
347            logger.error('No signature file found. Please sign\n %s\n' +
348                         'offline and place the signature at \n %s\n' +
349                         'or pass a different location ' +
350                         'using the --sig argument.\n',
351                         args.digf, args.sigf)
352            sys.exit(1)
353        else:
354            try:
355                if args.algo == 'TEE_ALG_RSASSA_PKCS1_PSS_MGF1_SHA256':
356                    key.verify(
357                        sig,
358                        img_digest,
359                        padding.PSS(
360                            mgf=padding.MGF1(chosen_hash),
361                            salt_length=digest_len
362                        ),
363                        utils.Prehashed(chosen_hash)
364                    )
365                elif args.algo == 'TEE_ALG_RSASSA_PKCS1_V1_5_SHA256':
366                    key.verify(
367                        sig,
368                        img_digest,
369                        padding.PKCS1v15(),
370                        utils.Prehashed(chosen_hash)
371                    )
372            except exceptions.InvalidSignature:
373                logger.error('Verification failed, ignoring given signature.')
374                sys.exit(1)
375
376            write_image_with_signature(sig)
377            logger.info('Successfully applied signature.')
378
379    def verify_ta():
380        # Extract header
381        [magic,
382         img_type,
383         img_size,
384         algo_value,
385         digest_len,
386         sig_len] = struct.unpack('<IIIIHH', img[:SHDR_SIZE])
387
388        # Extract digest and signature
389        start, end = SHDR_SIZE, SHDR_SIZE + digest_len
390        digest = img[start:end]
391
392        start, end = end, SHDR_SIZE + digest_len + sig_len
393        signature = img[start:end]
394
395        # Extract UUID and TA version
396        start, end = end, end + 16 + 4
397        [uuid, ta_version] = struct.unpack('<16sI', img[start:end])
398
399        if magic != SHDR_MAGIC:
400            raise Exception("Unexpected magic: 0x{:08x}".format(magic))
401
402        if img_type != SHDR_BOOTSTRAP_TA:
403            raise Exception("Unsupported image type: {}".format(img_type))
404
405        if algo_value not in algo.values():
406            raise Exception('Unrecognized algorithm: 0x{:08x}'
407                            .format(algo_value))
408
409        # Verify signature against hash digest
410        if algo_value == 0x70414930:
411            key.verify(
412                signature,
413                digest,
414                padding.PSS(
415                    mgf=padding.MGF1(chosen_hash),
416                    salt_length=digest_len
417                ),
418                utils.Prehashed(chosen_hash)
419            )
420        else:
421            key.verify(
422                signature,
423                digest,
424                padding.PKCS1v15(),
425                utils.Prehashed(chosen_hash)
426            )
427
428        h = hashes.Hash(chosen_hash, default_backend())
429
430        # sizeof(struct shdr)
431        h.update(img[:SHDR_SIZE])
432
433        # sizeof(struct shdr_bootstrap_ta)
434        h.update(img[start:end])
435
436        # raw image
437        start = end
438        end += img_size
439        h.update(img[start:end])
440
441        if digest != h.finalize():
442            raise Exception('Hash digest does not match')
443
444        logger.info('Trusted application is correctly verified.')
445
446    # dispatch command
447    {
448        'sign-enc': sign_encrypt_ta,
449        'digest': generate_digest,
450        'generate-digest': generate_digest,
451        'stitch': stitch_ta,
452        'stitch-ta': stitch_ta,
453        'verify': verify_ta,
454    }.get(args.command, 'sign_encrypt_ta')()
455
456
457if __name__ == "__main__":
458    main()
459