From 099e8524ca2a0804749fcc39c3548477fd6710c8 Mon Sep 17 00:00:00 2001 From: Damien Ji Date: Thu, 9 Jan 2025 17:32:23 +0800 Subject: [PATCH] telink: extend and update mfg_tool - add plaintext vid, pid & discriminator at the beginning. - add optional argument --secure-programming-verification and --chip-id to generate factory data and independent DAC. Signed-off-by: Damien Ji --- scripts/tools/telink/mfg_tool.py | 141 ++++++++++++++++++++++++++++--- 1 file changed, 131 insertions(+), 10 deletions(-) diff --git a/scripts/tools/telink/mfg_tool.py b/scripts/tools/telink/mfg_tool.py index dcc41d788215d9..750c19f80a4f49 100644 --- a/scripts/tools/telink/mfg_tool.py +++ b/scripts/tools/telink/mfg_tool.py @@ -33,6 +33,9 @@ import pyqrcode from intelhex import IntelHex +from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes +from cryptography.hazmat.backends import default_backend + TOOLS = { 'spake2p': None, 'chip-cert': None, @@ -455,10 +458,15 @@ def write_device_unique_data(args, out_dirs, pai_cert): dacs = use_dac_cert_from_args(args, out_dirs) else: dacs = generate_dac_cert(int(row['Index']), args, out_dirs, int(row['Discriminator']), - int(row['PIN Code']), pai_cert['key_pem'], pai_cert['cert_pem']) - - nvs_memory_append('dac_cert', read_der_file(dacs[0])) - nvs_memory_append('dac_key', read_key_bin_file(dacs[1])) + int(row['PIN Code']), pai_cert['key_pem'], pai_cert['cert_pem']) + dac_cert_storage = read_der_file(dacs[0]) + dac_key_storage = read_key_bin_file(dacs[1]) + if args.secure_programming_verification == False: + nvs_memory_append('dac_cert', dac_cert_storage) + nvs_memory_append('dac_key', dac_key_storage) + else: + logger.info("Secure programming verification enabled; DAC and its keys are not stored directly into factory data") + nvs_memory_append('pai_cert', read_der_file(pai_cert['cert_der'])) nvs_memory_append('cert_dclrn', read_der_file(args.cert_dclrn)) @@ -472,7 +480,42 @@ def write_device_unique_data(args, out_dirs, pai_cert): return dacs -def generate_partition(args, out_dirs): +def aes_encrypt(key, data): + # Ensure data is 16 bytes (AES block size) + assert len(data) == 16, "Data block for AES must be 16 bytes." + assert len(key) == 16, "AES key must be 16 bytes (128 bits)." + + cipher = Cipher(algorithms.AES(key), modes.ECB(), backend=default_backend()) + encryptor = cipher.encryptor() + return encryptor.update(data) + encryptor.finalize() + +def save_dac_cert_and_keys(dac_cert, dac_key, chip_id, file_path): + with open(file_path, 'wb') as f: + # Write DAC private key length (2 bytes, little-endian) + dac_key_len = len(dac_key) + f.write(dac_key_len.to_bytes(2, 'little')) + + # Encrypt DAC private key in two 16-byte segments + encrypted_key_part1 = aes_encrypt(chip_id, dac_key[:16]) + encrypted_key_part2 = aes_encrypt(chip_id, dac_key[16:32]) + + # Write the encrypted DAC private key parts (32 bytes total) + f.write(encrypted_key_part1) + f.write(encrypted_key_part2) + + # Pad the file with 0xFF to reach the 100th byte + f.write(b'\xFF' * (100 - f.tell())) + + # Write DAC certificate length (2 bytes, little-endian) + dac_cert_len = len(dac_cert) + f.write(dac_cert_len.to_bytes(2, 'little')) + + # Write DAC certificate data + f.write(dac_cert) + + print(f"DAC certificate and key have been saved to {file_path}") + +def generate_partition(args, dacs_cert, out_dirs): logger.info('Generating partition image: offset: 0x{:X} size: 0x{:X}'.format(args.offset, args.size)) cbor_data = cbor.dumps(NVS_MEMORY) # Create hex file @@ -480,8 +523,57 @@ def generate_partition(args, out_dirs): raise ValueError("generated CBOR file exceeds declared maximum partition size! {} > {}".format(len(cbor_data), args.size)) ih = IntelHex() ih.putsz(args.offset, cbor_data) - ih.write_hex_file(os.sep.join([out_dirs['output'], 'factory_data.hex']), True) - ih.tobinfile(os.sep.join([out_dirs['output'], 'factory_data.bin'])) + # ih.write_hex_file(os.sep.join([out_dirs['output'], 'factory_data.hex']), True) + # ih.tobinfile(os.sep.join([out_dirs['output'], 'factory_data.bin'])) + + output_dir = out_dirs['output'] + hex_file_path = os.path.join(output_dir, 'factory_data.hex') + bin_file_path = os.path.join(output_dir, 'factory_data.bin') + ih.write_hex_file(hex_file_path, True) + ih.tobinfile(bin_file_path) + + if args.secure_programming_verification: + dac_cert_key_file_path = os.path.join(output_dir, 'dac_cert_key.bin') + dac_cert_storage = read_der_file(dacs_cert[0]) + logger.info("dac_cert_storaget (Hex): {}".format(dac_cert_storage.hex())) + dac_key_storage = read_key_bin_file(dacs_cert[1]) + logger.info("dac_key_storage (Hex): {}".format(dac_key_storage.hex())) + chip_id_bytes = bytes.fromhex(args.chip_id) + logger.info("chip_id_bytes (Hex): {}".format(chip_id_bytes.hex())) + save_dac_cert_and_keys(dac_cert_storage, dac_key_storage, chip_id_bytes, dac_cert_key_file_path) + logger.info(f"DAC certificate and key have been saved to {dac_cert_key_file_path}") + + with open(os.sep.join([out_dirs['output'], 'pin_disc.csv']), 'r') as csvf: + pin_disc_dict = csv.DictReader(csvf) + row = pin_disc_dict.__next__() + + # Extract Discriminator, PID, and VID + discriminator = int(row['Discriminator']) + pid = args.product_id + vid = args.vendor_id + + logger.info("Discriminator = {}".format(discriminator)) + logger.info("PID = 0x{:X}".format(pid)) + logger.info("VID = 0x{:X}".format(vid)) + + # Convert Discriminator, PID, and VID to bytes + discriminator_bytes = discriminator.to_bytes(2, byteorder='little') + pid_bytes = pid.to_bytes(2, byteorder='little') + vid_bytes = vid.to_bytes(2, byteorder='little') + + # Read the existing binary data + with open(bin_file_path, 'rb') as bin_file: + bin_data = bin_file.read() + + # Prepend the hex data to the binary data + extended_data = discriminator_bytes + pid_bytes + vid_bytes + bin_data + + # Write the combined data to a new file + ext_bin_file_path = os.path.join(output_dir, 'factory_data_ext.bin') + with open(ext_bin_file_path, 'wb') as ext_bin_file: + ext_bin_file.write(extended_data) + + logger.info(f"Extended binary data written to {ext_bin_file_path}") def generate_json_summary(args, out_dirs, pai_certs, dacs_cert, serial_num: str): @@ -544,7 +636,10 @@ def add_additional_kv(args, serial_num): nvs_memory_append('rd_uid', args.rd_id_uid) # Add the serial-num - nvs_memory_append('sn', serial_num) + if args.disable_serial_num_storage: + logger.info("Secure programming verification enabled; skipping serial-num") + else: + nvs_memory_append('sn', serial_num) nvs_memory_append('version', FACTORY_DATA_VERSION) @@ -601,7 +696,7 @@ def base64_str(s): return base64.b64decode(s) help='Commissionable device discovery networking technology. \ 0:WiFi-SoftAP, 1:BLE, 2:On-network. Default is BLE.', choices=[0, 1, 2]) - # Device insrance information + # Device instance information dev_inst_args = parser.add_argument_group('Device instance information options') dev_inst_args.add_argument('-v', '--vendor-id', type=allow_any_int, required=False, help='Vendor id') dev_inst_args.add_argument('--vendor-name', type=str, required=False, help='Vendor name') @@ -646,8 +741,34 @@ def base64_str(s): return base64.b64decode(s) help='Partition offset - an address in devices NVM memory, where factory data will be stored') part_gen_args.add_argument('--size', type=allow_any_int, help='The maximum partition size') + secure_args = parser.add_argument_group('Secure programming verification options') + secure_args.add_argument("--secure-programming-verification", action="store_true", + help="Enable secure programming mode. When set, the script will perform additional steps for secure programming verification.") + secure_args.add_argument("--chip-id", required=False, type=str, help="Chip ID in hex format (32 hex characters).") + secure_args.add_argument("--disable_serial_num_storage", action="store_true", + help="Disable storage of serial-num in factorydata.") + args = parser.parse_args() + if args.secure_programming_verification: + if not args.chip_id: + logger.error("--chip-id is required when --secure-programming-verification is enabled.") + sys.exit(1) + + if args.count > 1: + logger.error("--count cannot be greater than 1 when --secure-programming-verification is enabled.") + sys.exit(1) + + # Additional validation on chip-id format + if args.chip_id: + try: + chip_id_bytes = bytes.fromhex(args.chip_id) + if len(chip_id_bytes) != 16: + raise ValueError() + except ValueError: + logger.error("--chip-id must be a valid 16-byte hex string (32 hex characters).") + sys.exit(1) + # Validate in-tree parameter if args.count > 1 and args.in_tree: logger.error('Option --in-tree can not be use together with --count > 1') @@ -743,7 +864,7 @@ def main(): if args.paa or args.pai: pai_cert = setup_root_certificates(args, out_dirs) dacs_cert = write_device_unique_data(args, out_dirs, pai_cert) - generate_partition(args, out_dirs) + generate_partition(args, dacs_cert, out_dirs) generate_json_summary(args, out_dirs, pai_cert, dacs_cert, serial_num_str) dev_sn_file.close()