From f23757f3a11dc65bb86ad74735935a921be04a4d Mon Sep 17 00:00:00 2001 From: SandyYeow <34238012+SandyYeow@users.noreply.github.com> Date: Wed, 15 May 2024 09:17:26 +0800 Subject: [PATCH] [tools] support factorydata generator in window (#118) --- doc/matter/FactoryData_guide.md | 63 +++- .../factorydata/ameba_factory_window.py | 290 ++++++++++++++++++ 2 files changed, 351 insertions(+), 2 deletions(-) create mode 100644 tools/matter/factorydata/ameba_factory_window.py diff --git a/doc/matter/FactoryData_guide.md b/doc/matter/FactoryData_guide.md index c0d91455..a777dce1 100644 --- a/doc/matter/FactoryData_guide.md +++ b/doc/matter/FactoryData_guide.md @@ -2,7 +2,7 @@ Follow this guide to generate and use your own factory data instead of using test data -### Prerequisites +### Prerequisites (Linux) Build chip-cert tool @@ -26,6 +26,15 @@ Install python dependency Make sure your firmware is built with `DCONFIG_ENABLE_AMEBA_FACTORY_DATA` enabled in the `Makefile.include.gen` under`ambd_matter/realtek_amebaD_va0_example/GCC-RELEASE/project_hp/asdk` +### Prerequisites (Window) + +To use the Factory Data TOols in Window, ensure the follow software is installed in you Cygwin Environment: +1. Unzip 6.0-16 or above +2. Zip 3.0-1 or above +3. Python3 3.9.16 or above + +Make sure your firmware is built with `CONFIG_ENABLE_AMEBA_FACTORY_DATA` enabled in the **core** and **main** Matter library makefile + ### Generating your own Certificates and Keys - Note that this is only for testing and development @@ -56,7 +65,7 @@ Generate the certs and keys ./gen-certs.sh The certs and keys will be outputted in `connectedhomeip/myattestation` -### Generate Factory Data Binary File +### Generate Factory Data Binary File (Linux) Navigate to below directory, if matter's environment is activated, deactivate it @@ -108,6 +117,56 @@ Example command, run from `tools/matter/factorydata` After running the script successfully, `ameba_factory.bin` should be generated in the same directory +### Generate Factory Data Binary File (Window) + +Navigate to the directory where python script is stored. + +Run the `ameba_factory_window.py` python script, passing in neccessary arguments + + python3 ameba_factory_window.py \ + --command gen-verifier \ + -d \ + -p \ + --dac_cert \ + --dac_key \ + --pai_cert \ + --cd \ + --vendor-id \ + --vendor-name \ + --product-id \ + --product-name \ + --hw-ver \ + --hw-ver-str \ + --mfg-date \ + --serial-num \ + --rd-id-uid \ + --factorydata-key <32-bytes key to encrypt factorydata, hexstring, without "0x" in front> \ + --factorydata-iv <16-bytes iv to encrypt factorydata, hexstring, without "0x" in front> + +Example command, run from `tools/matter/factorydata` + + python3 ameba_factory_window.py \ + --command gen-verifier \ + -d 3840 \ + -p 20202021 \ + --dac_cert ../../../third_party/connectedhomeip/myattestation/Chip-Test-DAC-8888-9999-Cert.der \ + --dac_key ../../../third_party/connectedhomeip/myattestation/Chip-Test-DAC-8888-9999-Key.der \ + --pai_cert ../../../third_party/connectedhomeip/myattestation/Chip-Test-PAI-8888-NoPID-Cert.der \ + --cd ../../../third_party/connectedhomeip/myattestation/Chip-Test-CD-8888-9999.der \ + --vendor-id 0x8888 \ + --vendor-name ameba \ + --product-id 0x9999 \ + --product-name amebad \ + --hw-ver 1 \ + --hw-ver-str "1.0" \ + --mfg-date 2022-12-01 \ + --serial-num 123456 \ + --rd-id-uid 00112233445566778899aabbccddeeff \ + --factorydata-key ff0102030405060708090a0b0c0d0e0fff0102030405060708090a0b0c0d0e0f \ + --factorydata-iv ff0102030405060708090a0b0c0d0e0f + +After running the script successfully, `ameba_factory.bin` should be generated in the same directory + ### Factory Data Encryption If you want to encrypt the factorydata, pass in `factorydata-key`, if you want to use an IV for encryption, pass in `factorydata-iv` as well diff --git a/tools/matter/factorydata/ameba_factory_window.py b/tools/matter/factorydata/ameba_factory_window.py new file mode 100644 index 00000000..1406e194 --- /dev/null +++ b/tools/matter/factorydata/ameba_factory_window.py @@ -0,0 +1,290 @@ +#!/usr/bin/env python3 +# +# Copyright (c) 2022 Project CHIP Authors +# All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +import os +import sys +import logging +import hashlib +import argparse +import subprocess +import base64 +import struct +from cryptography.hazmat.backends import default_backend +from cryptography.hazmat.primitives.serialization import load_der_private_key +import ameba_factory_pb2 as ameba_factory + +from ecdsa.curves import NIST256p +from base64 import b64encode + +INVALID_PASSCODES = [00000000, 11111111, 22222222, 33333333, 44444444, 55555555, + 66666666, 77777777, 88888888, 99999999, 12345678, 87654321] + +# Length of `w0s` and `w1s` elements +WS_LENGTH = NIST256p.baselen + 8 + +FACTORY_DATA = ameba_factory.FactoryDataProvider() + +# split the private key der file to public and private keys, return private key +def get_raw_private_key_der(der_file: str, password: str): + """ Split given der file to get separated key pair consisting of public and private keys. + Args: + der_file (str): Path to .der file containing public and private keys + password (str): Password to decrypt Keys. It can be None, and then KEY is not encrypted. + Returns: + hex string: return a hex string containing extracted and decrypted private KEY from given .der file. + """ + try: + with open(der_file, 'rb') as file: + key_data = file.read() + if password is None: + logging.warning("KEY password has not been provided. It means that DAC key is not encrypted.") + keys = load_der_private_key(key_data, password, backend=default_backend()) + private_key = keys.private_numbers().private_value.to_bytes(32, byteorder='big') + print(private_key) + + return private_key + + except IOError or ValueError: + return None + +# check_str_range: for validate_args +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) + +# check_int_range: for validate_args +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) + +# validate_args: check if all the arguments are valid +def validate_args(args): + # Validate the passcode + if args.passcode is not None: + if ((args.passcode < 0x0000001 and args.passcode > 0x5F5E0FE) or (args.passcode in INVALID_PASSCODES)): + logging.error('Invalid passcode:' + str(args.passcode)) + 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.rd_id_uid, 32, 32, 'Rotating device Unique id') + + logging.info('Discriminator:{} Passcode:{}'.format(args.discriminator, args.passcode)) + +def generate_verifier(passcode: int, salt_base64: str, iterations: int) -> bytes: + if not 0 <= passcode <= 99999999: + raise argparse.ArgumentTypeError('passcode out of range') + + if passcode in INVALID_PASSCODES: + raise argparse.ArgumentTypeError('invalid passcode') + + if not 1000 <= iterations <= 100000: + raise argparse.ArgumentTypeError('iteration count out of range') + + salt = base64.b64decode(salt_base64) + + if not 16 <= len(salt) <= 32: + raise argparse.ArgumentTypeError('invalid salt length') + + ws = hashlib.pbkdf2_hmac('sha256', struct.pack('