From 207262610d40b1032696c10efaf31e5e06148cee Mon Sep 17 00:00:00 2001 From: Shubham Patil Date: Tue, 5 Jul 2022 20:08:49 +0530 Subject: [PATCH] [ESP32] Options to include Device instance information to factory partition (#20269) * [ESP32] Options to include Device instance information to factory partition * Apply suggestions from code review Co-authored-by: Tennessee Carmel-Veilleux * Apply suggestions from code review Co-authored-by: tehampson Co-authored-by: Tennessee Carmel-Veilleux Co-authored-by: tehampson --- .../tools/generate_esp32_chip_factory_bin.py | 212 +++++++++++++++--- 1 file changed, 186 insertions(+), 26 deletions(-) diff --git a/scripts/tools/generate_esp32_chip_factory_bin.py b/scripts/tools/generate_esp32_chip_factory_bin.py index 51f3f37c38fece..76975d706317af 100755 --- a/scripts/tools/generate_esp32_chip_factory_bin.py +++ b/scripts/tools/generate_esp32_chip_factory_bin.py @@ -41,6 +41,105 @@ TOOLS = {} +FACTORY_DATA = { + # CommissionableDataProvider + 'discriminator': { + 'type': 'data', + 'encoding': 'u32', + 'value': None, + }, + 'iteration-count': { + 'type': 'data', + 'encoding': 'u32', + 'value': None, + }, + 'salt': { + 'type': 'data', + 'encoding': 'string', + 'value': None, + }, + 'verifier': { + 'type': 'data', + 'encoding': 'string', + 'value': None, + }, + + # CommissionableDataProvider + 'dac-cert': { + 'type': 'file', + 'encoding': 'binary', + 'value': None, + }, + 'dac-key': { + 'type': 'file', + 'encoding': 'binary', + 'value': None, + }, + 'dac-pub-key': { + 'type': 'file', + 'encoding': 'binary', + 'value': None, + }, + 'pai-cert': { + 'type': 'file', + 'encoding': 'binary', + 'value': None, + }, + 'cert-dclrn': { + 'type': 'file', + 'encoding': 'binary', + 'value': None, + }, + + # DeviceInstanceInforProvider + 'vendor-id': { + 'type': 'data', + 'encoding': 'u32', + 'value': None, + }, + 'vendor-name': { + 'type': 'data', + 'encoding': 'string', + 'value': None, + }, + 'product-id': { + 'type': 'data', + 'encoding': 'u32', + 'value': None, + }, + 'product-name': { + 'type': 'data', + 'encoding': 'string', + 'value': None, + }, + 'serial-num': { + 'type': 'data', + 'encoding': 'string', + 'value': None, + }, + 'hardware-ver': { + 'type': 'data', + 'encoding': 'u32', + 'value': None, + }, + 'hw-ver-str': { + 'type': 'data', + 'encoding': 'string', + 'value': None, + }, + 'mfg-date': { + 'type': 'data', + 'encoding': 'string', + 'value': None, + }, + 'unique-id': { + 'type': 'data', + 'encoding': 'hex2bin', + 'value': None, + } +} + + def check_tools_exists(): TOOLS['spake2p'] = shutil.which('spake2p') if TOOLS['spake2p'] is None: @@ -48,6 +147,18 @@ def check_tools_exists(): sys.exit(1) +def check_str_range(s, min_len, max_len, name): + if s and ((len(s) < min_len) or (len(s) > max_len)): + logging.error('%s must be between %d and %d characters', name, min_len, max_len) + sys.exit(1) + + +def check_int_range(value, min_value, max_value, name): + if value and ((value < min_value) or (value > max_value)): + logging.error('%s is out of range, should be in range [%d, %d]', name, min_value, max_value) + sys.exit(1) + + def validate_args(args): # Validate the passcode if args.passcode is not None: @@ -55,10 +166,17 @@ def validate_args(args): logging.error('Invalid passcode:' + str(args.passcode)) sys.exit(1) - # Validate the discriminator - if (args.discriminator is not None) and (args.discriminator not in range(0x0000, 0x0FFF)): - logging.error('Invalid discriminator:' + str(args.discriminator)) - sys.exit(1) + check_int_range(args.discriminator, 0x0000, 0x0FFF, 'Discriminator') + check_int_range(args.product_id, 0x0000, 0xFFFF, 'Product id') + check_int_range(args.vendor_id, 0x0000, 0xFFFF, 'Vendor id') + check_int_range(args.hw_ver, 0x0000, 0xFFFF, 'Hardware version') + + check_str_range(args.serial_num, 1, 32, 'Serial number') + check_str_range(args.vendor_name, 1, 32, 'Vendor name') + check_str_range(args.product_name, 1, 32, 'Product name') + check_str_range(args.hw_ver_str, 1, 64, 'Hardware version string') + check_str_range(args.mfg_date, 8, 16, 'Manufacturing date') + check_str_range(args.unique_id, 32, 32, 'Unique id') logging.info('Discriminator:{} Passcode:{}'.format(args.discriminator, args.passcode)) @@ -80,6 +198,37 @@ def gen_spake2p_params(passcode): return dict(zip(output[0].split(','), output[1].split(','))) +def populate_factory_data(args, spake2p_params): + FACTORY_DATA['discriminator']['value'] = args.discriminator + FACTORY_DATA['iteration-count']['value'] = spake2p_params['Iteration Count'] + FACTORY_DATA['salt']['value'] = spake2p_params['Salt'] + FACTORY_DATA['verifier']['value'] = spake2p_params['Verifier'] + FACTORY_DATA['dac-cert']['value'] = os.path.abspath(args.dac_cert) + FACTORY_DATA['pai-cert']['value'] = os.path.abspath(args.pai_cert) + FACTORY_DATA['cert-dclrn']['value'] = os.path.abspath(args.cd) + FACTORY_DATA['dac-key']['value'] = os.path.abspath('dac_raw_privkey.bin') + FACTORY_DATA['dac-pub-key']['value'] = os.path.abspath('dac_raw_pubkey.bin') + + if args.serial_num is not None: + FACTORY_DATA['serial-num']['value'] = args.serial_num + if args.unique_id is not None: + FACTORY_DATA['unique-id']['value'] = args.unique_id + if args.mfg_date is not None: + FACTORY_DATA['mfg-date']['value'] = args.mfg_date + if args.vendor_id is not None: + FACTORY_DATA['vendor-id']['value'] = args.vendor_id + if args.vendor_name is not None: + FACTORY_DATA['vendor-name']['value'] = args.vendor_name + if args.product_id is not None: + FACTORY_DATA['product-id']['value'] = args.product_id + if args.product_name is not None: + FACTORY_DATA['product-name']['value'] = args.product_name + if args.hw_ver is not None: + FACTORY_DATA['hardware-ver']['value'] = args.hw_ver + if (args.hw_ver_str is not None): + FACTORY_DATA['hw-ver-str']['value'] = args.hw_ver_str + + def gen_raw_ec_keypair_from_der(key_file, pubkey_raw_file, privkey_raw_file): with open(key_file, 'rb') as f: key_data = f.read() @@ -104,24 +253,14 @@ def gen_raw_ec_keypair_from_der(key_file, pubkey_raw_file, privkey_raw_file): f.write(public_number_y.to_bytes(32, byteorder='big')) -def generate_nvs_bin(args, spake2p_params): - dac_raw_privkey = 'dac_raw_privkey.bin' - dac_raw_pubkey = 'dac_raw_pubkey.bin' - gen_raw_ec_keypair_from_der(args.dac_key, dac_raw_pubkey, dac_raw_privkey) - +def generate_nvs_bin(args): csv_content = 'key,type,encoding,value\n' csv_content += 'chip-factory,namespace,,\n' - csv_content += 'discriminator,data,u32,{}\n'.format(args.discriminator) - csv_content += 'iteration-count,data,u32,{}\n'.format(spake2p_params['Iteration Count']) - csv_content += 'salt,data,string,{}\n'.format(spake2p_params['Salt']) - csv_content += 'verifier,data,string,{}\n'.format(spake2p_params['Verifier']) - - csv_content += 'dac-cert,file,binary,{}\n'.format(os.path.abspath(args.dac_cert)) - csv_content += 'dac-key,file,binary,{}\n'.format(os.path.abspath(dac_raw_privkey)) - csv_content += 'dac-pub-key,file,binary,{}\n'.format(os.path.abspath(dac_raw_pubkey)) - csv_content += 'pai-cert,file,binary,{}\n'.format(os.path.abspath(args.pai_cert)) - csv_content += 'cert-dclrn,file,binary,{}\n'.format(os.path.abspath(args.cd)) + for k, v in FACTORY_DATA.items(): + if v['value'] is None: + continue + csv_content += f"{k},{v['type']},{v['encoding']},{v['value']}\n" with open('nvs_partition.csv', 'w') as f: f.write(csv_content) @@ -134,10 +273,6 @@ def generate_nvs_bin(args, spake2p_params): nvs_partition_gen.generate(nvs_args) - os.remove('nvs_partition.csv') - os.remove(dac_raw_privkey) - os.remove(dac_raw_pubkey) - def print_flashing_help(): logging.info('To flash the generated partition.bin, run the following command:') @@ -147,15 +282,24 @@ def print_flashing_help(): logging.info('default \"nvs\" partition addr is 0x9000') +def clean_up(): + os.remove('nvs_partition.csv') + os.remove(FACTORY_DATA['dac-pub-key']['value']) + os.remove(FACTORY_DATA['dac-key']['value']) + + def main(): def any_base_int(s): return int(s, 0) parser = argparse.ArgumentParser(description='Chip Factory NVS binary generator tool') + # These will be used by CommissionalbeDataProvider parser.add_argument('-p', '--passcode', type=any_base_int, required=True, - help='The discriminator for pairing, range: 0x01-0x5F5E0FE') + help='The setup passcode for pairing, range: 0x01-0x5F5E0FE') parser.add_argument('-d', '--discriminator', type=any_base_int, required=True, - help='The passcode for pairing, range: 0x00-0x0FFF') + help='The discriminator for pairing, range: 0x00-0x0FFF') + + # These will be used by DeviceAttestationCredentialsProvider parser.add_argument('--dac-cert', type=str, required=True, help='The path to the DAC certificate in der format') parser.add_argument('--dac-key', type=str, required=True, @@ -164,6 +308,19 @@ def any_base_int(s): return int(s, 0) help='The path to the PAI certificate in der format') parser.add_argument('--cd', type=str, required=True, help='The path to the certificate declaration der format') + + # These will be used by DeviceInstanceInfoProvider + parser.add_argument('--vendor-id', type=any_base_int, required=False, help='Vendor id') + parser.add_argument('--vendor-name', type=str, required=False, help='Vendor name') + parser.add_argument('--product-id', type=any_base_int, required=False, help='Product id') + parser.add_argument('--product-name', type=str, required=False, help='Product name') + parser.add_argument('--hw-ver', type=any_base_int, required=False, help='Hardware version') + parser.add_argument('--hw-ver-str', type=str, required=False, help='Hardware version string') + parser.add_argument('--mfg-date', type=str, required=False, help='Manufacturing date in format YYYY-MM-DD') + parser.add_argument('--serial-num', type=str, required=False, help='Serial number') + parser.add_argument('--unique-id', type=str, required=False, + help='128-bit unique identifier, provide 32-byte hex string, e.g. "1234567890abcdef1234567890abcdef"') + parser.add_argument('-s', '--size', type=any_base_int, required=False, default=0x6000, help='The size of the partition.bin, default: 0x6000') @@ -171,8 +328,11 @@ def any_base_int(s): return int(s, 0) validate_args(args) check_tools_exists() spake2p_params = gen_spake2p_params(args.passcode) - generate_nvs_bin(args, spake2p_params) + populate_factory_data(args, spake2p_params) + gen_raw_ec_keypair_from_der(args.dac_key, FACTORY_DATA['dac-pub-key']['value'], FACTORY_DATA['dac-key']['value']) + generate_nvs_bin(args) print_flashing_help() + clean_up() if __name__ == "__main__":