#!/usr/bin/env python # SPDX-FileCopyrightText: 2020-2022 Espressif Systems (Shanghai) CO LTD # SPDX-License-Identifier: Apache-2.0 import argparse import hashlib import hmac import json import os import struct import subprocess import sys from cryptography.hazmat.backends import default_backend from cryptography.hazmat.primitives import serialization from cryptography.hazmat.primitives.asymmetric import rsa from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes from cryptography.utils import int_to_bytes try: import nvs_partition_gen as nvs_gen except ImportError: idf_path = os.getenv('IDF_PATH') if not idf_path or not os.path.exists(idf_path): raise Exception('IDF_PATH not found') sys.path.insert(0, os.path.join(idf_path, 'components', 'nvs_flash', 'nvs_partition_generator')) import nvs_partition_gen as nvs_gen # Check python version is proper or not to avoid script failure assert sys.version_info >= (3, 6, 0), 'Python version too low.' esp_ds_data_dir = 'esp_ds_data' # hmac_key_file is generated when HMAC_KEY is calculated, it is used when burning HMAC_KEY to efuse hmac_key_file = esp_ds_data_dir + '/hmac_key.bin' # csv and bin filenames are default filenames for nvs partition files created with this script csv_filename = esp_ds_data_dir + '/pre_prov.csv' bin_filename = esp_ds_data_dir + '/pre_prov.bin' expected_json_path = os.path.join('build', 'config', 'sdkconfig.json') # Targets supported by the script supported_targets = {'esp32s2', 'esp32c3'} supported_key_size = {'esp32s2':[1024, 2048, 3072, 4096], 'esp32c3':[1024, 2048, 3072]} # @return # on success idf_target - value of the IDF_TARGET read from build/config/sdkconfig.json # on failure None def get_idf_target(): if os.path.exists(expected_json_path): sdkconfig = json.load(open(expected_json_path)) idf_target_read = sdkconfig['IDF_TARGET'] return idf_target_read else: print('ERROR: IDF_TARGET has not been set for the supported targets,' "\nplase execute command \"idf.py set-target {TARGET}\" in the example directory") return None def load_privatekey(key_file_path, password=None): key_file = open(key_file_path, 'rb') key = key_file.read() key_file.close() return serialization.load_pem_private_key(key, password=password, backend=default_backend()) def number_as_bytes(number, pad_bits=None): """ Given a number, format as a little endian array of bytes """ result = int_to_bytes(number)[::-1] while pad_bits is not None and len(result) < (pad_bits // 8): result += b'\x00' return result # @return # c : ciphertext_c # iv : initialization vector # key_size : key size of the RSA private key in bytes. # @input # privkey : path to the RSA private key # priv_key_pass : path to the RSA privaete key password # hmac_key : HMAC key value ( to calculate DS params) # idf_target : The target chip for the script (e.g. esp32s2, esp32c3) # @info # The function calculates the encrypted private key parameters. # Consult the DS documentation (available for the ESP32-S2) in the esp-idf programming guide for more details about the variables and calculations. def calculate_ds_parameters(privkey, priv_key_pass, hmac_key, idf_target): private_key = load_privatekey(privkey, priv_key_pass) if not isinstance(private_key, rsa.RSAPrivateKey): print('ERROR: Only RSA private keys are supported') sys.exit(-1) if hmac_key is None: print('ERROR: hmac_key cannot be None') sys.exit(-2) priv_numbers = private_key.private_numbers() pub_numbers = private_key.public_key().public_numbers() Y = priv_numbers.d M = pub_numbers.n key_size = private_key.key_size if key_size not in supported_key_size[idf_target]: print('ERROR: Private key size {0} not supported for the target {1},\nthe supported key sizes are {2}' .format(key_size, idf_target, str(supported_key_size[idf_target]))) sys.exit(-1) iv = os.urandom(16) rr = 1 << (key_size * 2) rinv = rr % pub_numbers.n mprime = - rsa._modinv(M, 1 << 32) mprime &= 0xFFFFFFFF length = key_size // 32 - 1 # get max supported key size for the respective target max_len = max(supported_key_size[idf_target]) aes_key = hmac.HMAC(hmac_key, b'\xFF' * 32, hashlib.sha256).digest() md_in = number_as_bytes(Y, max_len) + \ number_as_bytes(M, max_len) + \ number_as_bytes(rinv, max_len) + \ struct.pack('