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