1#!/usr/bin/env python3
2# SPDX-License-Identifier: BSD-2-Clause
3#
4# Copyright (c) 2023, Linaro Limited
5#
6
7import sys
8
9
10def hex_parse(str):
11    try:
12        h = bytes.fromhex(str)
13    except ValueError as e:
14        try:
15            # Try to pad with a '0' nibble in front
16            h = bytes.fromhex('0' + str)
17            print('Odd number of nibbles in hexadecimal string',
18                  file=sys.stderr)
19            raise e
20        except ValueError:
21            raise e
22    return h
23
24
25def get_args():
26    import argparse
27    import textwrap
28
29    parser = argparse.ArgumentParser(
30        allow_abbrev=False,
31        description='''Derive an RPMB key from the Hardware Unique Key used
32                       by OP-TEE and the CID of the RPMB.''',
33        epilog='''Note that the derived key matches what the
34                  __huk_subkey_derive() would produce. If huk_subkey_derive()
35                  is overridden to call another function, please don't use
36                  this script''')
37
38    parser.add_argument('--quiet', action='store_true', default=False,
39                        help='''Gives only the hexstring of the RPMB key as
40                                output, intended for scripting''')
41    parser.add_argument('--testkey', action='store_true', default=False,
42                        help='''Outputs the hardcoded test key''')
43    parser.add_argument('--huk', type=hex_parse,
44                        help='''Hardware Unique Key (16 bytes), as returned
45                                by the platform specific function
46                                tee_otp_get_hw_unique_key() in OP-TEE''')
47    parser.add_argument('--cid', type=hex_parse, help='CID (16 bytes)')
48    parser.add_argument('--compat', action='store_true', default=False,
49                        help='''Generates a backwards compatible key,
50                                only to be used if OP-TEE is build with
51                                CFG_CORE_HUK_SUBKEY_COMPAT=y''')
52
53    return parser.parse_args()
54
55
56def derive_key(huk, cid, compat):
57    import struct
58    from cryptography.hazmat.primitives import hashes, hmac
59
60    # Prepare the CID and Clear the PRV (Product revision) and CRC (CRC7
61    # checksum) fields as OP-TEE does.
62    data = bytearray(cid)
63    data[9] = 0
64    data[15] = 0
65
66    # This is how __huk_subkey_derive() is implemented, if huk_subkey_derive()
67    # is overridden the key derived here may not match what OP-TEE is using
68    #
69    # HUK is as tee_otp_get_hw_unique_key() in OP-TEE returns it
70    h = hmac.HMAC(huk, hashes.SHA256())
71    if not compat:
72        usage_word = struct.pack('<I', 0)
73        h.update(usage_word)
74    h.update(data)
75    return h.finalize()
76
77
78def main():
79    args = get_args()
80
81    if args.testkey:
82        if args.cid or args.huk or args.compat:
83            print('--cid, --huk, or --compat '
84                  'cannot be given together with --testkey')
85            sys.exit(1)
86        # The test key hardcoded in OP-TEE
87        key = bytes.fromhex('''D3 EB 3E C3 6E 33 4C 9F
88                               98 8C E2 C0 B8 59 54 61
89                               0D 2B CF 86 64 84 4D F2
90                               AB 56 E6 C6 1B B7 01 E4''')
91    else:
92        if not args.cid:
93            print('--cid is required without --testkey')
94            sys.exit(1)
95
96        if not args.huk:
97            print('--huk is required without --testkey')
98            sys.exit(1)
99
100        if len(args.cid) != 16:
101            print(f'Invalid CID length, expected 16 bytes got {len(args.cid)}',
102                  file=sys.stderr)
103            sys.exit(1)
104
105        if len(args.huk) != 16:
106            print(f'Invalid HUK length, expected 16 bytes got {len(args.huk)}',
107                  file=sys.stderr)
108            sys.exit(1)
109
110        if not args.quiet:
111            print(f'HUK:      {args.huk.hex()} length {len(args.huk)}')
112            print(f'RPMB CID: {args.cid.hex()} length {len(args.cid)}')
113
114        key = derive_key(args.huk, args.cid, args.compat)
115
116    if args.quiet:
117        print(key.hex())
118    else:
119        print(f'RPMB key: {key.hex()}')
120        print(f'          length {len(key)}')
121        if args.testkey:
122            print('''
123*********************************************************************
124*** Please note that the test key should only be used for testing ***
125*** purposes since it's well known and the same for all devices.  ***
126*********************************************************************''')
127        else:
128            print('''
129Please take care to double-check the provided input since writing the RPMB
130key is an irreversible step.''')
131
132
133if __name__ == "__main__":
134    main()
135