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