Skip to content

Commit

Permalink
telink: extend and update mfg_tool
Browse files Browse the repository at this point in the history
- 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 <[email protected]>
  • Loading branch information
damien0x0023 committed Jan 9, 2025
1 parent f23f72a commit 099e852
Showing 1 changed file with 131 additions and 10 deletions.
141 changes: 131 additions & 10 deletions scripts/tools/telink/mfg_tool.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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))
Expand All @@ -472,16 +480,100 @@ 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
if len(cbor_data) > args.size:
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):
Expand Down Expand Up @@ -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)

Expand Down Expand Up @@ -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')
Expand Down Expand Up @@ -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')
Expand Down Expand Up @@ -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()
Expand Down

0 comments on commit 099e852

Please sign in to comment.