diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ed572e0 --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +**/.pio +**/.vscode +**/.DS_Store + +**/__pycache__ +*.py[cod] diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..cfc144c --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,53 @@ +# +# CMakeLists.txt file for cryptoauthlib +# +cmake_minimum_required(VERSION 3.5) +set(CRYPTOAUTHLIB_DIR "cryptoauthlib/lib" ) +set(COMPONENT_SRCDIRS "${CRYPTOAUTHLIB_DIR}/atcacert" + "${CRYPTOAUTHLIB_DIR}/calib" + "${CRYPTOAUTHLIB_DIR}/crypto" + "${CRYPTOAUTHLIB_DIR}/crypto/hashes" + "${CRYPTOAUTHLIB_DIR}/host" + "${CRYPTOAUTHLIB_DIR}/mbedtls" + "${CRYPTOAUTHLIB_DIR}" + "${CRYPTOAUTHLIB_DIR}/../app/tng" + "port" + ) + +set(COMPONENT_SRCS "${CRYPTOAUTHLIB_DIR}/hal/atca_hal.c" + "${CRYPTOAUTHLIB_DIR}/hal/hal_freertos.c" + "${CRYPTOAUTHLIB_DIR}/hal/hal_esp32_timer.c" + "${CRYPTOAUTHLIB_DIR}/../third_party/atca_mbedtls_patch.c" + ) + +set(COMPONENT_INCLUDEDIRS "${CRYPTOAUTHLIB_DIR}/" + "${CRYPTOAUTHLIB_DIR}/hal" + "${CRYPTOAUTHLIB_DIR}/../app/tng" + "${CRYPTOAUTHLIB_DIR}/../third_party" + "port" + ) + +set(COMPONENT_PRIV_INCLUDEDIRS "port/include") + +set(COMPONENT_REQUIRES "mbedtls" "freertos" "driver" "Core2-for-AWS-IoT-EduKit") + +# Don't include the default interface configurations from cryptoauthlib +set(COMPONENT_EXCLUDE_SRCS "${CRYPTOAUTHLIB_DIR}/atca_cfgs.c") +set(COMPONENT_CFLAGS "ESP32") + +idf_component_register( SRC_DIRS "${COMPONENT_SRCDIRS}" + INCLUDE_DIRS "${COMPONENT_INCLUDEDIRS}" + PRIV_INCLUDE_DIRS "${COMPONENT_PRIV_INCLUDEDIRS}" + PRIV_REQUIRES "${COMPONENT_PRIV_REQUIRES}" + REQUIRES "${COMPONENT_REQUIRES}" + EXCLUDE_SRCS "${COMPONENT_EXCLUDE_SRCS}" + ) + +if ( IDF_VERSION_MAJOR EQUAL 4 AND IDF_VERSION_MINOR LESS_EQUAL 3 AND IDF_VERSION_PATCH LESS 1 AND CONFIG_SOFTWARE_CRYPTO_SUPPORT ) + target_compile_definitions(${COMPONENT_LIB} PUBLIC "-DATCA_ENABLE_DEPRECATED" ) + message( FATAL_ERROR "Major: ${IDF_VERSION_MAJOR} Min: ${IDF_VERSION_MINOR} Patch: ${IDF_VERSION_PATCH}" ) +endif() + +target_sources(${COMPONENT_LIB} PRIVATE ${COMPONENT_SRCS}) +target_compile_definitions(${COMPONENT_LIB} PRIVATE ${COMPONENT_CFLAGS}) +target_compile_options(${COMPONENT_LIB} PRIVATE -Wno-pointer-sign) diff --git a/Kconfig b/Kconfig new file mode 100644 index 0000000..fe873c0 --- /dev/null +++ b/Kconfig @@ -0,0 +1,49 @@ +menu "esp-cryptoauthlib" + + choice ATECC608A_TYPE + prompt "Choose the type of ATECC608A chip" + default ATECC608A_TNG + help + There are three types of ATECC608A ( Secure Element) chips, which are + Trust & GO, TrustFLex and Trust Custom. For more details consult README + file of esp_cryptoauth_utility which is part of esp-cryptoauthlib component. + config ATECC608A_TNG + bool "ATECC608A chip type Trust & GO" + config ATECC608A_TFLEX + bool "ATECC608A chip type TrustFlex" + config ATECC608A_TCUSTOM + bool "ATECC608A chip type TrustCustom" + endchoice + + config ATCA_MBEDTLS_ECDSA + bool "Enable Hardware ECDSA keys for mbedTLS" + depends on MBEDTLS_ECDSA_C + help + Enable Hardware ECDSA + + config ATCA_MBEDTLS_ECDSA_SIGN + bool "Enable ATECC608A sign operations in mbedTLS" + depends on ATCA_MBEDTLS_ECDSA + select MBEDTLS_ATCA_HW_ECDSA_SIGN + select MBEDTLS_ECP_DP_SECP256R1_ENABLED + + config ATCA_MBEDTLS_ECDSA_VERIFY + bool "Enable ATECC608A verify operations in mbedTLS" + depends on ATCA_MBEDTLS_ECDSA + select MBEDTLS_ATCA_HW_ECDSA_VERIFY + select MBEDTLS_ECP_DP_SECP256R1_ENABLED + + config ATCA_I2C_SDA_PIN + int "I2C SDA pin used to communicate with the ATECC608A" + default 21 + + config ATCA_I2C_SCL_PIN + int "I2C SCL pin used to communicate with the ATECC608A" + default 22 + + config ATCA_I2C_ADDRESS + hex "I2C device address of the ATECC608A" + default 0xC0 if ATECC608A_TCUSTOM + default 0x6C if ATECC608A_TFLEX + default 0x6A if ATECC608A_TNG +endmenu # cryptoauthlib \ No newline at end of file diff --git a/README.md b/README.md index 7f92204..47bde66 100644 --- a/README.md +++ b/README.md @@ -1,17 +1,43 @@ -## My Project +# ESP-CRYPTOAUTHLIB -TODO: Fill this README out! +This is a port of Microchip's [cryptoauthlib](https://github.com/MicrochipTech/cryptoauthlib) to work on the [M5Stack Core2 for AWS IoT EduKit](https://aws.amazon.com/iot/edukit/#Get_started_with_AWS_IoT_EduKit) using the ESP-IDF. It contains necessary build support to use cryptoauthlib with ESP-IDF as well as `esp_cryptoauthlib_utility` for configuring and provisiong ATECC608 chip integrated with the Core2 for AWS. The cryptoauthlib folder is a submodule of Microchip's [cryptoauthlib](https://github.com/MicrochipTech/cryptoauthlib). This ported library requires the [Core2 for AWS IoT EduKit board support package](https://github.com/m5stack/Core2-for-AWS-IoT-EduKit) (BSP) to be included as well for thread-safe access to the I2C bus using Rop Gonggrijp's I2C Manager and power using the ported Mika Tuupola's AXP192 driver. -Be sure to: +## Requirements -* Change the title in this README -* Edit your repository description on GitHub +* [ESP-IDF](https://github.com/espressif/esp-idf) version should be `release/v4.3` or newer. +* Environment variable `IDF_PATH` should be set -## Security +## How to use esp-cryptoauthlib with ESP-IDF +--- +There are two ways to use **esp-cryptoauthlib** and the Core2 for AWS BSP in your project -See [CONTRIBUTING](CONTRIBUTING.md#security-issue-notifications) for more information. +1) Directly add **esp-cryptoauthlib** and the BSP as a component in your project with following three commands. -## License + (First change directory (cd) to your project directory) +``` + mkdir components + cd components + git clone https://github.com/espressif/esp-cryptoauthlib.git --recurse-submodules + git clone https://github.com/m5stack/Core2-for-AWS-IoT-EduKit.git --recurse-submodules -This library is licensed under the MIT-0 License. See the LICENSE file. +``` +2) Add **esp-cryptoauthlib** as an extra component in your project. +* Download **esp-cryptoauthlib** and the **Core2 for AWS BSP** with: +``` + git clone https://github.com/espressif/esp-cryptoauthlib.git --recurse-submodules + git clone https://github.com/m5stack/Core2-for-AWS-IoT-EduKit.git --recurse-submodules +``` + +If you have downloaded the repos without using the --recurse-submodules argument, you need to run this command in each repo: +``` +git submodule update --init --recursive +``` + +* Include `esp-cryptoauthlib` in `ESP-IDF` with setting `EXTRA_COMPONENT_DIRS` in CMakeLists.txt/Makefile of your project.For reference see [Optional Project Variables](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-guides/build-system.html#optional-project-variables) + + + +## How to configure and provision ATECC608A on Core2 for AWS +The python utilty `esp_cryptoauth_utility` helps to configure, generate resources as well as provision ATECC608 chip integrated with the Core2 for AWS. +For detailed instructions on how to use the utility please refer utility [README.md](esp_cryptoauth_utility/README.md) diff --git a/THIRD-PARTY-LICENSES.txt b/THIRD-PARTY-LICENSES.txt new file mode 100644 index 0000000..ac41564 --- /dev/null +++ b/THIRD-PARTY-LICENSES.txt @@ -0,0 +1,27 @@ +** ESP-CRYPTOAUTHLIB; version None -- +https://github.com/espressif/esp-cryptoauthlib +Copyright (c) 2018 Espressif Systems (Shanghai) PTE LTD +Copyright (c) 2015-2020 Microchip Technology Inc. and its subsidiaries. + +(c) 2015-2021 Microchip Technology Inc. and its subsidiaries. + +Subject to your compliance with these terms, you may use the Microchip Software +and any derivatives exclusively with Microchip products. It is your +responsibility to comply with third party license terms applicable to your +use of third party software (including open source software) that may +accompany Microchip Software. + +Redistribution of this Microchip Software in source or binary form is allowed +and must include the above terms of use and the following disclaimer with the +distribution and accompanying materials. + +THIS SOFTWARE IS SUPPLIED BY MICROCHIP "AS IS". NO WARRANTIES, WHETHER +EXPRESS, IMPLIED OR STATUTORY, APPLY TO THIS SOFTWARE, INCLUDING ANY IMPLIED +WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY, AND FITNESS FOR A +PARTICULAR PURPOSE. IN NO EVENT WILL MICROCHIP BE LIABLE FOR ANY INDIRECT, +SPECIAL, PUNITIVE, INCIDENTAL OR CONSEQUENTIAL LOSS, DAMAGE, COST OR EXPENSE +OF ANY KIND WHATSOEVER RELATED TO THE SOFTWARE, HOWEVER CAUSED, EVEN IF +MICROCHIP HAS BEEN ADVISED OF THE POSSIBILITY OR THE DAMAGES ARE FORESEEABLE. +TO THE FULLEST EXTENT ALLOWED BY LAW, MICROCHIP'S TOTAL LIABILITY ON ALL +CLAIMS IN ANY WAY RELATED TO THIS SOFTWARE WILL NOT EXCEED THE AMOUNT OF +FEES, IF ANY, THAT YOU HAVE PAID DIRECTLY TO MICROCHIP FOR THIS SOFTWARE. diff --git a/component.mk b/component.mk new file mode 100644 index 0000000..0533951 --- /dev/null +++ b/component.mk @@ -0,0 +1,41 @@ +# +# Component Makefile +# +CRYPTOAUTHLIB_DIR := cryptoauthlib/lib + +COMPONENT_SRCDIRS := $(CRYPTOAUTHLIB_DIR)/atcacert \ + $(CRYPTOAUTHLIB_DIR)/calib \ + $(CRYPTOAUTHLIB_DIR)/crypto \ + $(CRYPTOAUTHLIB_DIR)/crypto/hashes \ + $(CRYPTOAUTHLIB_DIR)/host \ + $(CRYPTOAUTHLIB_DIR)/mbedtls \ + $(CRYPTOAUTHLIB_DIR)/../app/tng \ + $(CRYPTOAUTHLIB_DIR) \ + port + +COMPONENT_OBJS := $(foreach compsrcdir,$(COMPONENT_SRCDIRS),$(patsubst %.c,%.o,$(wildcard $(COMPONENT_PATH)/$(compsrcdir)/*.c))) \ + $(CRYPTOAUTHLIB_DIR)/hal/atca_hal.o \ + $(CRYPTOAUTHLIB_DIR)/hal/hal_freertos.o \ + ${CRYPTOAUTHLIB_DIR}/hal/hal_esp32_timer.c \ + $(CRYPTOAUTHLIB_DIR)/../third_party/atca_mbedtls_patch.o + +# Make relative by removing COMPONENT_PATH from all found object paths +COMPONENT_OBJS := $(patsubst $(COMPONENT_PATH)/%,%,$(COMPONENT_OBJS)) + +# Don't include the default interface configurations from cryptoauthlib +COMPONENT_OBJEXCLUDE := $(CRYPTOAUTHLIB_DIR)/atca_cfgs.o + +# Add the hal directory back in for source search paths +COMPONENT_SRCDIRS += $(CRYPTOAUTHLIB_DIR)/hal \ + $(CRYPTOAUTHLIB_DIR)/../third_party/hal/esp32 + +COMPONENT_ADD_INCLUDEDIRS := $(CRYPTOAUTHLIB_DIR) \ + $(CRYPTOAUTHLIB_DIR)/hal \ + $(CRYPTOAUTHLIB_DIR)/../app/tng \ + ${CRYPTOAUTHLIB_DIR}/../third_party \ + port + +COMPONENT_PRIV_INCLUDEDIRS := port/include + +# Library requires some global defines +CFLAGS+=-DESP32 -Wno-pointer-sign diff --git a/esp_cryptoauth_utility/LICENSE b/esp_cryptoauth_utility/LICENSE new file mode 100644 index 0000000..dbdce70 --- /dev/null +++ b/esp_cryptoauth_utility/LICENSE @@ -0,0 +1,37 @@ +########################################################################### +(c) 2017 Microchip Technology Inc. and its subsidiaries. You may use this +software and any derivatives exclusively with Microchip products. + +THIS SOFTWARE IS SUPPLIED BY MICROCHIP "AS IS". NO WARRANTIES, WHETHER +EXPRESS, IMPLIED OR STATUTORY, APPLY TO THIS SOFTWARE, INCLUDING ANY IMPLIED +WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY, AND FITNESS FOR A PARTICULAR +PURPOSE, OR ITS INTERACTION WITH MICROCHIP PRODUCTS, COMBINATION WITH ANY +OTHER PRODUCTS, OR USE IN ANY APPLICATION. + +IN NO EVENT WILL MICROCHIP BE LIABLE FOR ANY INDIRECT, SPECIAL, PUNITIVE, +INCIDENTAL OR CONSEQUENTIAL LOSS, DAMAGE, COST OR EXPENSE OF ANY KIND +WHATSOEVER RELATED TO THE SOFTWARE, HOWEVER CAUSED, EVEN IF MICROCHIP HAS BEEN +ADVISED OF THE POSSIBILITY OR THE DAMAGES ARE FORESEEABLE. TO THE FULLEST +EXTENT ALLOWED BY LAW, MICROCHIP'S TOTAL LIABILITY ON ALL CLAIMS IN ANY WAY +RELATED TO THIS SOFTWARE WILL NOT EXCEED THE AMOUNT OF FEES, IF ANY, THAT YOU +HAVE PAID DIRECTLY TO MICROCHIP FOR THIS SOFTWARE. + +MICROCHIP PROVIDES THIS SOFTWARE CONDITIONALLY UPON YOUR ACCEPTANCE OF THESE +TERMS. + +############################################################################# + + Copyright 2020 Espressif Systems (Shanghai) Co., Ltd. + +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. +############################################################################## diff --git a/esp_cryptoauth_utility/README.md b/esp_cryptoauth_utility/README.md new file mode 100644 index 0000000..69ac1f5 --- /dev/null +++ b/esp_cryptoauth_utility/README.md @@ -0,0 +1,76 @@ +# ESP_CRYPTOAUTH_UTILITY +# Description + The python utility helps to configure and provision `ATECC608` chip on `ESP32-WROOM-32SE` module.The [ESP32-WROOM-32SE](https://www.espressif.com/sites/default/files/documentation/esp32-wroom-32se_datasheet_en.pdf) module has Microchip's [ATECC608A](https://www.microchip.com/wwwproducts/en/ATECC608A) integrated on the module. The latest ESP32-WROOM-32SE modules have the Microchip's [ATECC608B](https://www.microchip.com/en-us/products/security-ics/trust-platform/trust-and-go) integrated on the module. + There are currently three types of ATECC608 which are [Trust & Go](https://www.microchip.com/wwwproducts/en/ATECC608A-TNGTLS), [TrustFlex](https://www.microchip.com/wwwproducts/en/ATECC608A-TFLXTLS) and [TrustCustom](https://www.microchip.com/wwwproducts/en/ATECC608A). `Trust & Go` and `TrustFlex` chips are preconfigured by the manufacturer (Microchip) so we only need to generate manifest file for those chips. `TrustCustom` type of chips are not configured, so for `TrustCustom` type of chips need to be first configured and then provisioned with a newly generated device certificate and key pair. The script automatically detects which type of ATECC608 chip is integrated with `ESP32-WROOM-32SE` so it will proceed to next required steps on its own. + +# Hardware Required +It requires [ESP32-WROOM-32SE](https://www.espressif.com/sites/default/files/documentation/esp32-wroom-32se_datasheet_en.pdf) which has Microchip's [ATECC608A](https://www.microchip.com/wwwproducts/en/ATECC608A) (Secure Element) integrated on the module. + +An ESP32 to which ATECC608 is connected with I2C interface can also be used by setting the I2C pin configurations (see below option about providing I2C pin cfg). + +> Note: It is recommended to change directory to `esp_cryptoauth_utility` to execute all following commands if not already done. + +## Install python dependancies +To use the utility some python depencancies must be installed with following command(current directory should be `esp_cryptoauth_utility` for executing the command). + + pip install -r requirements.txt + +## Step 1:- Generate Signer Certificate +Signer cert and key pair: +* In case of `TrustCustom` chips ,these certificate and key pair are used to sign the device cert which is going to be generted. + +* In case of `Trust & Go` and `TrustFlex` devices the device certs are already signed by microchip signer cert, and the signer cert and key pair generated in this step are used to sign the manifest file. + +By default the utility uses the `sample_signer_cert.pem` located in the `sample_certs` folder.if you want to keep using default certificats, then directly proceed to next step(Step 2). + +Create a signer key and signer cert by executing following commands sequentially. The second command will ask some details about certificate such as `ORG, CN` which are needed to be filled by the user. + + `Important`: The signer cert `CN`_(Common Name)_ must end with `FFFF` as it is required by the `cert2certdef.py` (file by microchip) to create its definition properly. for e.g valid CN = `Sample Signer FFFF`( This is compulsory only in case of `TrustCustom` type of chips and not for the other two). + + openssl ecparam -out signerkey.pem -name prime256v1 -genkey + + openssl req -new -x509 -key signerkey.pem -out signercert.pem -days 365 + +## Step 2:- Provision the module/Generate manifest file + +* The tool will automatically detect the type of ATECC608 chip connected to ESP module and perform its intended task which are as follows. + + * For `TrustCustom` type of ATECC608 chip first configure ATECC608 chip with its default configuration options.The tool will create a device cert by generating a private key on slot 0 of the module, passing the CSR to host, sign the CSR with signer cert generated in step above. To set validity of device cert please refer [device_cert_validity](README.md#set-validity-of-device-cert-for-trustcustom). save the device cert on the ATECC chip as well as on the host machine as `device_cert.pem`,it also saves the cert definitions in `output_files` folder for future use. + + * For `Trust & Go` and `TrustFlex` type of ATECC608 devices this script will generate the manifest file with the name of chip serial number.The manifest file will be signed with the signer cert generated above. The generated manifest file should be registered with the cloud to register the device certificate. + +The command is as follows: + +``` +python secure_cert_mfg.py --signer-cert signercert.pem --signer-cert-private-key signerkey.pem --port /UART/COM/PORT +``` +> Note: The names `signercert.pem` and `signerkey.pem` denote the name of the signer cert and key files respectively, you can replace them with `relative/path/to/you/signer/cert` and `key` respectively. The `UART/COM/PORT` represents the host machine COM port to which your ESP32-WROOM-32SE is connected.Please refer [check serial port](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/get-started/establish-serial-connection.html#check-port-on-windows) for obtaining the serial port connected to ESP. + +If you do not provide `signer-cert` and `signer_key` in above command, `sample_signer_cert.pem` stored at `sample_certs` will be used. + +--- +### Provide I2C pin configuration (for modules other than ESP32-WROOM32-SE) +The I2C pins of the ESP32 to which ATECC608 chip is connected can be provided as a parameter to the python script. +The command is as follows: +``` +python secure_cert_mfg.py --i2c-sda-pin /* SDA pin no */ --i2c-scl-pin /* SCL pin no */ /* + other options */ +``` +When no pin configurations are provided to the script, by default SDA=16, SCL=17 will be used which is the I2C configuration of ESP32-WROOM-32SE. + +### Find type of ATECC608 chip connected to ESP32-WROOM32-SE. + +The command is as follows: +``` +python secure_cert_mfg.py --port /serial/port --type +``` +It will print the type of ATECC608 chip connected to ESP32-WROOM-32SE on console. + +### Set validity of device cert for TrustCustom +The validity (in years) of device certificate generated for `TrustCustom` chips from the time of generation of cert can be set with `--valid-for-years` option. Please refer the following command: +``` +python secure_cert_mfg.py --port /serial/port --valid-for-years /Years +``` + +>Note: If `--valid-for-years` is not provided then default value for validity of certiticates will be used, which is 40 years. + + diff --git a/esp_cryptoauth_utility/helper_scripts/__init__.py b/esp_cryptoauth_utility/helper_scripts/__init__.py new file mode 100644 index 0000000..cc2e6ae --- /dev/null +++ b/esp_cryptoauth_utility/helper_scripts/__init__.py @@ -0,0 +1,4 @@ +from . import cert_sign +from .serial import load_app_stub, cmd_interpreter, esp_cmd_check_ok +from .cert2certdef import esp_create_cert_def_str +from .manifest import generate_manifest_file diff --git a/esp_cryptoauth_utility/helper_scripts/cert2certdef.py b/esp_cryptoauth_utility/helper_scripts/cert2certdef.py new file mode 100644 index 0000000..7976012 --- /dev/null +++ b/esp_cryptoauth_utility/helper_scripts/cert2certdef.py @@ -0,0 +1,817 @@ +#!/usr/bin/env python +# ############################################################################# +# (c) 2017 Microchip Technology Inc. and its subsidiaries. You may use this +# software and any derivatives exclusively with Microchip products. +# +# THIS SOFTWARE IS SUPPLIED BY MICROCHIP "AS IS". NO WARRANTIES, WHETHER +# EXPRESS, IMPLIED OR STATUTORY, APPLY TO THIS SOFTWARE, INCLUDING ANY IMPLIED +# WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY, AND FITNESS FOR A PARTICULAR +# PURPOSE, OR ITS INTERACTION WITH MICROCHIP PRODUCTS, COMBINATION WITH ANY +# OTHER PRODUCTS, OR USE IN ANY APPLICATION. +# +# IN NO EVENT WILL MICROCHIP BE LIABLE FOR ANY INDIRECT, SPECIAL, PUNITIVE, +# INCIDENTAL OR CONSEQUENTIAL LOSS, DAMAGE, COST OR EXPENSE OF ANY KIND +# WHATSOEVER RELATED TO THE SOFTWARE, HOWEVER CAUSED, EVEN IF MICROCHIP HAS BEEN +# ADVISED OF THE POSSIBILITY OR THE DAMAGES ARE FORESEEABLE. TO THE FULLEST +# EXTENT ALLOWED BY LAW, MICROCHIP'S TOTAL LIABILITY ON ALL CLAIMS IN ANY WAY +# RELATED TO THIS SOFTWARE WILL NOT EXCEED THE AMOUNT OF FEES, IF ANY, THAT YOU +# HAVE PAID DIRECTLY TO MICROCHIP FOR THIS SOFTWARE. +# +# MICROCHIP PROVIDES THIS SOFTWARE CONDITIONALLY UPON YOUR ACCEPTANCE OF THESE +# TERMS. +############################################################################### +# Copyright 2020 Espressif Systems (Shanghai) Co., Ltd. +# +# 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 string +import datetime +import argparse +from pyasn1_modules import pem, rfc2459, rfc2314 +from pyasn1.codec.der import decoder, encoder +from pyasn1.type import univ + + +def main(): + # Create argument parser to document script use + parser = argparse.ArgumentParser(description='Generate atcacert_def_t structure from sample certificate.') + group = parser.add_mutually_exclusive_group(required=True) + group.add_argument( + '--signer-cert', + dest='signer_cert_filename', + nargs='?', + default=None, + metavar='file', + help='Generate signer certificate definition from sample certificate.') + group.add_argument( + '--device-cert', + dest='device_cert_filename', + nargs='?', + default=None, + metavar='file', + help='Generate device certificate definition from sample certificate.') + group.add_argument( + '--device-csr', + dest='device_csr_filename', + nargs='?', + default=None, + metavar='file', + help='Generate device CSR definition from sample CSR.') + args = parser.parse_args() + + if args.signer_cert_filename is not None: + cert_der = pem.readPemFromFile(open(args.signer_cert_filename)) + print(gen_cert_def_c_signer(cert_der)) + return + + if args.device_cert_filename is not None: + cert_der = pem.readPemFromFile(open(args.device_cert_filename)) + print(gen_cert_def_c_device(cert_der)) + return + + if args.device_csr_filename is not None: + csr_der = pem.readPemFromFile( + open(args.device_csr_filename), + startMarker='-----BEGIN CERTIFICATE REQUEST-----', + endMarker='-----END CERTIFICATE REQUEST-----') + print(gen_cert_def_c_device_csr(csr_der)) + return + + +def set_time_params(params, cert, name): + if name == 'notBefore': + param_prefix = 'issue_date' + elif name == 'notAfter': + param_prefix = 'expire_date' + + info = cert_time_offset_length(cert, name) + params[param_prefix + '_cert_loc_offset'] = info['offset'] + params[param_prefix + '_cert_loc_count'] = info['length'] + + if info['length'] == 13: + params[param_prefix + '_format'] = 'DATEFMT_RFC5280_UTC' + time_str = str(cert['tbsCertificate']['validity'][name][0]) + dt = datetime.datetime.strptime(time_str, '%y%m%d%H%M%SZ') + elif info['length'] == 15: + params[param_prefix + '_format'] = 'DATEFMT_RFC5280_GEN' + time_str = str(cert['tbsCertificate']['validity'][name][1]) + dt = datetime.datetime.strptime(time_str, '%Y%m%d%H%M%SZ') + else: + raise ValueError(name + ' date has invalid length') + + return dt + + +def bin_to_c_hex(data): + c_hex = "" + for i in range(0, len(data)): + if i != 0: + c_hex += ',' + if i % 16 == 0: + if i != 0: + c_hex += '\n' + c_hex += ' ' + elif i % 8 == 0: + c_hex += ' ' + else: + c_hex += ' ' + try: + c_hex += '0x%02x' % int(data[i].encode('hex'), 16) + except AttributeError: + c_hex += '0x%02x' % data[i] + + return c_hex + + +def cert_sn_offset_length(cert): + sn_der = bytearray(encoder.encode(cert['tbsCertificate']['serialNumber'])) + int_info = der_value_offset_length(sn_der) + msb_idx = int_info['offset'] + if sn_der[msb_idx] & 0x80: + if sn_der[msb_idx] == 0x81: + sn_der[msb_idx] = 0x82 + else: + sn_der[msb_idx] = 0x81 + else: + if sn_der[msb_idx] == 0x01: + sn_der[msb_idx] = 0x02 + else: + sn_der[msb_idx] = 0x01 + cert_der = encoder.encode(cert) + cert_mod = decoder.decode(cert_der, asn1Spec=rfc2459.Certificate())[0] + cert_mod['tbsCertificate']['serialNumber'] = decoder.decode(bytes(sn_der))[0] + + return {'offset':diff_offset(cert_der, encoder.encode(cert_mod)), 'length':int_info['length']} + + +def cert_signer_id_offset_length(cert, name): + name_der = bytearray(encoder.encode(cert['tbsCertificate'][name])) + name_der = name_der.replace(b'FFFF', b'0000') + + cert_der = encoder.encode(cert) + cert_mod = decoder.decode(cert_der, asn1Spec=rfc2459.Certificate())[0] + cert_mod['tbsCertificate'][name] = decoder.decode(bytes(name_der))[0] + + return {'offset':diff_offset(cert_der, encoder.encode(cert_mod)), 'length':4} + + +def cert_time_offset_length(cert, name): + cert_der = encoder.encode(cert) + cert_mod = decoder.decode(cert_der, asn1Spec=rfc2459.Certificate())[0] + time_str = str(cert_mod['tbsCertificate']['validity'][name].getComponent()) + time_str = chr(ord(time_str[0]) + 1) + time_str[1:] + cert_mod['tbsCertificate']['validity'][name] = cert_mod['tbsCertificate']['validity'][name].getComponent().clone( + value=time_str) + + return {'offset': diff_offset(cert_der, encoder.encode(cert_mod)), 'length': len(time_str)} + + +def cert_public_key_offset_length(cert): + pk_der = bytearray(encoder.encode(cert['tbsCertificate']['subjectPublicKeyInfo']['subjectPublicKey'])) + pk_info = der_value_offset_length(pk_der) + # Skip the unused bits field and key compression byte + pk_der[pk_info['offset'] + 2] ^= 0xFF + + cert_der = encoder.encode(cert) + cert_mod = decoder.decode(cert_der, asn1Spec=rfc2459.Certificate())[0] + cert_mod['tbsCertificate']['subjectPublicKeyInfo']['subjectPublicKey'] = decoder.decode(bytes(pk_der))[0] + + return {'offset':diff_offset(cert_der, encoder.encode(cert_mod)), 'length':pk_info['length'] - 2} + + +def csr_public_key_offset_length(csr): + pk_der = bytearray(encoder.encode(csr['certificationRequestInfo']['subjectPublicKeyInfo']['subjectPublicKey'])) + pk_info = der_value_offset_length(pk_der) + # Skip the unused bits field and key compression byte + pk_der[pk_info['offset'] + 2] ^= 0xFF + + csr_der = encoder.encode(csr) + csr_mod = decoder.decode(csr_der, asn1Spec=rfc2314.CertificationRequest())[0] + csr_mod['certificationRequestInfo']['subjectPublicKeyInfo']['subjectPublicKey'] = decoder.decode(bytes(pk_der))[0] + + return {'offset':diff_offset(csr_der, encoder.encode(csr_mod)), 'length':pk_info['length'] - 2} + + +def cert_subj_key_id_offset_length(cert): + cert_der = encoder.encode(cert) + cert_mod = decoder.decode(cert_der, asn1Spec=rfc2459.Certificate())[0] + for ext in cert_mod['tbsCertificate']['extensions']: + if ext['extnID'] == rfc2459.id_ce_subjectKeyIdentifier: + extn_value = decoder.decode(ext['extnValue'])[0] + key_id = bytearray(decoder.decode(extn_value, asn1Spec=rfc2459.SubjectKeyIdentifier())[0]) + key_id[0] ^= 0xFF # Change first byte + + extn_value = rfc2459.SubjectKeyIdentifier(key_id) + ext['extnValue'] = univ.OctetString(encoder.encode(extn_value)) + + return {'offset':diff_offset(cert_der, encoder.encode(cert_mod)), 'length':len(key_id)} + return None + + +def cert_auth_key_id_offset_length(cert): + cert_der = encoder.encode(cert) + cert_mod = decoder.decode(cert_der, asn1Spec=rfc2459.Certificate())[0] + for ext in cert_mod['tbsCertificate']['extensions']: + if ext['extnID'] == rfc2459.id_ce_authorityKeyIdentifier: + extn_value = decoder.decode(ext['extnValue'])[0] + auth_key_id = decoder.decode(extn_value, asn1Spec=rfc2459.AuthorityKeyIdentifier())[0] + key_id = bytearray(auth_key_id['keyIdentifier']) + key_id[0] ^= 0xFF # Change first byte + + auth_key_id['keyIdentifier'] = auth_key_id['keyIdentifier'].clone(value=key_id) + ext['extnValue'] = univ.OctetString(encoder.encode(auth_key_id)) + + return {'offset':diff_offset(cert_der, encoder.encode(cert_mod)), 'length':len(key_id)} + return None + + +def cert_tbs_offset_length(cert): + cert_der = encoder.encode(cert) + cert_info = der_value_offset_length(cert_der) + tbs_info = der_value_offset_length(cert_der[cert_info['offset']:]) + + return {'offset':cert_info['offset'], 'length':(tbs_info['offset'] + tbs_info['length'])} + + +def cert_sig_offset_length(cert): + cert_der = encoder.encode(cert) + + cert_info = der_value_offset_length(cert_der) + offset = cert_info['offset'] + + tbs_info = der_value_offset_length(cert_der[offset:]) + offset += tbs_info['offset'] + tbs_info['length'] + + alg_info = der_value_offset_length(cert_der[offset:]) + offset += alg_info['offset'] + alg_info['length'] + + sig_info = der_value_offset_length(cert_der[offset:]) + + return {'offset':offset, 'length':(sig_info['offset'] + sig_info['length'])} + + +def der_value_offset_length(der): + """Returns the offset and length of the value part of the DER tag-length-value object.""" + + tag_len = 1 # Assume 1 byte tag + if(isinstance(der[tag_len], str)): + tag_value = int(der[tag_len].encode('hex'), 16) + else: + tag_value = der[tag_len] + if tag_value < 0x80: + # Length is short-form, only 1 byte + len_len = 1 + len = int(tag_value) + else: + # Length is long-form, lower 7 bits indicates how many additional bytes are required + len_len = (tag_value & 0x7F) + 1 + try: + len = int(der[tag_len + 1:tag_len + len_len].encode('hex'), 16) + except AttributeError: + len = int().from_bytes(der[tag_len + 1:tag_len + len_len], byteorder='big', signed=False) + return {'offset':tag_len + len_len, 'length':len} + + +def diff_offset(base, diff): + """Return the index where the two parameters differ.""" + if len(base) != len(diff): + raise ValueError('len(base)=%d != len(diff)=%d' % (len(base), len(diff))) + for i in range(0, len(base)): + if base[i] != diff[i]: + return i + return None + + +cert_def_1_signer_c = """ +#include "atcacert/atcacert_def.h" + +uint8_t g_signer_1_ca_public_key[64] = { +${ca_public_key} +}; + +const atcacert_cert_element_t g_cert_elements_1_signer[] = { + { + .id = "IssueDate", + .device_loc = { + .zone = DEVZONE_DATA, + .slot = 14, + .is_genkey = 0, + .offset = 35-${issue_date_cert_loc_count}, + .count = ${issue_date_cert_loc_count} + }, + .cert_loc = { + .offset = ${issue_date_cert_loc_offset}, + .count = ${issue_date_cert_loc_count} + } + }, + { + .id = "ExpireDate", + .device_loc = { + .zone = DEVZONE_DATA, + .slot = 14, + .is_genkey = 0, + .offset = 50-${expire_date_cert_loc_count}, + .count = ${expire_date_cert_loc_count} + }, + .cert_loc = { + .offset = ${expire_date_cert_loc_offset}, + .count = ${expire_date_cert_loc_count} + } + } +}; + +const uint8_t g_cert_template_1_signer[] = { +${cert_template} +}; + +const atcacert_def_t g_cert_def_1_signer = { + .type = CERTTYPE_X509, + .template_id = 1, + .chain_id = 0, + .private_key_slot = 0, + .sn_source = SNSRC_STORED, + .cert_sn_dev_loc = { + .zone = DEVZONE_DATA, + .slot = 14, + .is_genkey = 0, + .offset = 20-${cert_sn_cert_loc_count}, + .count = ${cert_sn_cert_loc_count} + }, + .issue_date_format = ${issue_date_format}, + .expire_date_format = ${expire_date_format}, + .tbs_cert_loc = { + .offset = ${tbs_cert_loc_offset}, + .count = ${tbs_cert_loc_count} + }, + .expire_years = ${expire_years}, + .public_key_dev_loc = { + .zone = DEVZONE_DATA, + .slot = 11, + .is_genkey = 0, + .offset = 0, + .count = 72 + }, + .comp_cert_dev_loc = { + .zone = DEVZONE_DATA, + .slot = 12, + .is_genkey = 0, + .offset = 0, + .count = 72 + }, + .std_cert_elements = { + { // STDCERT_PUBLIC_KEY + .offset = ${public_key_cert_loc_offset}, + .count = ${public_key_cert_loc_count} + }, + { // STDCERT_SIGNATURE + .offset = ${signature_cert_loc_offset}, + .count = ${signature_cert_loc_count} + }, + { // STDCERT_ISSUE_DATE + .offset = ${issue_date_cert_loc_offset}, + .count = ${issue_date_cert_loc_count} + }, + { // STDCERT_EXPIRE_DATE + .offset = ${expire_date_cert_loc_offset}, + .count = ${expire_date_cert_loc_count} + }, + { // STDCERT_SIGNER_ID + .offset = ${signer_id_cert_loc_offset}, + .count = ${signer_id_cert_loc_count} + }, + { // STDCERT_CERT_SN + .offset = ${cert_sn_cert_loc_offset}, + .count = ${cert_sn_cert_loc_count} + }, + { // STDCERT_AUTH_KEY_ID + .offset = ${auth_key_id_cert_loc_offset}, + .count = ${auth_key_id_cert_loc_count} + }, + { // STDCERT_SUBJ_KEY_ID + .offset = ${subj_key_id_cert_loc_offset}, + .count = ${subj_key_id_cert_loc_count} + } + }, + .cert_elements = g_cert_elements_1_signer, + .cert_elements_count = sizeof(g_cert_elements_1_signer) / sizeof(g_cert_elements_1_signer[0]), + .cert_template = g_cert_template_1_signer, + .cert_template_size = sizeof(g_cert_template_1_signer) +}; +""" + + +cert_def_2_device_c = """ +#include "atcacert/atcacert_def.h" + +const uint8_t g_cert_template_2_device[] = { +${cert_template} +}; + +const atcacert_def_t g_cert_def_2_device = { + .type = CERTTYPE_X509, + .template_id = 2, + .chain_id = 0, + .private_key_slot = 0, + .sn_source = SNSRC_PUB_KEY_HASH, + .cert_sn_dev_loc = { + .zone = DEVZONE_NONE, + .slot = 0, + .is_genkey = 0, + .offset = 0, + .count = 0 + }, + .issue_date_format = ${issue_date_format}, + .expire_date_format = ${expire_date_format}, + .tbs_cert_loc = { + .offset = ${tbs_cert_loc_offset}, + .count = ${tbs_cert_loc_count} + }, + .expire_years = ${expire_years}, + .public_key_dev_loc = { + .zone = DEVZONE_DATA, + .slot = 0, + .is_genkey = 1, + .offset = 0, + .count = 64 + }, + .comp_cert_dev_loc = { + .zone = DEVZONE_DATA, + .slot = 10, + .is_genkey = 0, + .offset = 0, + .count = 72 + }, + .std_cert_elements = { + { // STDCERT_PUBLIC_KEY + .offset = ${public_key_cert_loc_offset}, + .count = ${public_key_cert_loc_count} + }, + { // STDCERT_SIGNATURE + .offset = ${signature_cert_loc_offset}, + .count = ${signature_cert_loc_count} + }, + { // STDCERT_ISSUE_DATE + .offset = ${issue_date_cert_loc_offset}, + .count = ${issue_date_cert_loc_count} + }, + { // STDCERT_EXPIRE_DATE + .offset = ${expire_date_cert_loc_offset}, + .count = ${expire_date_cert_loc_count} + }, + { // STDCERT_SIGNER_ID + .offset = ${signer_id_cert_loc_offset}, + .count = ${signer_id_cert_loc_count} + }, + { // STDCERT_CERT_SN + .offset = ${cert_sn_cert_loc_offset}, + .count = ${cert_sn_cert_loc_count} + }, + { // STDCERT_AUTH_KEY_ID + .offset = ${auth_key_id_cert_loc_offset}, + .count = ${auth_key_id_cert_loc_count} + }, + { // STDCERT_SUBJ_KEY_ID + .offset = ${subj_key_id_cert_loc_offset}, + .count = ${subj_key_id_cert_loc_count} + } + }, + .cert_elements = NULL, + .cert_elements_count = 0, + .cert_template = g_cert_template_2_device, + .cert_template_size = sizeof(g_cert_template_2_device) +}; +""" + + +cert_def_3_device_csr_c = """ +#include "atcacert/atcacert_def.h" + +const uint8_t g_csr_template_3_device[] = { +${cert_template} +}; + +const atcacert_def_t g_csr_def_3_device = { + .type = CERTTYPE_X509, + .template_id = 3, + .chain_id = 0, + .private_key_slot = 0, + .sn_source = SNSRC_PUB_KEY_HASH, + .cert_sn_dev_loc = { + .zone = DEVZONE_NONE, + .slot = 0, + .is_genkey = 0, + .offset = 0, + .count = 0 + }, + .issue_date_format = DATEFMT_RFC5280_UTC, + .expire_date_format = DATEFMT_RFC5280_UTC, + .tbs_cert_loc = { + .offset = ${tbs_cert_loc_offset}, + .count = ${tbs_cert_loc_count} + }, + .expire_years = 0, + .public_key_dev_loc = { + .zone = DEVZONE_NONE, + .slot = 0, + .is_genkey = 1, + .offset = 0, + .count = 64 + }, + .comp_cert_dev_loc = { + .zone = DEVZONE_NONE, + .slot = 0, + .is_genkey = 0, + .offset = 0, + .count = 0 + }, + .std_cert_elements = { + { // STDCERT_PUBLIC_KEY + .offset = ${public_key_cert_loc_offset}, + .count = ${public_key_cert_loc_count} + }, + { // STDCERT_SIGNATURE + .offset = ${signature_cert_loc_offset}, + .count = ${signature_cert_loc_count} + }, + { // STDCERT_ISSUE_DATE + .offset = 0, + .count = 0 + }, + { // STDCERT_EXPIRE_DATE + .offset = 0, + .count = 0 + }, + { // STDCERT_SIGNER_ID + .offset = 0, + .count = 0 + }, + { // STDCERT_CERT_SN + .offset = 0, + .count = 0 + }, + { // STDCERT_AUTH_KEY_ID + .offset = 0, + .count = 0 + }, + { // STDCERT_SUBJ_KEY_ID + .offset = 0, + .count = 0 + } + }, + .cert_elements = NULL, + .cert_elements_count = 0, + .cert_template = g_csr_template_3_device, + .cert_template_size = sizeof(g_csr_template_3_device) +}; +""" + + +def gen_cert_def_c_signer(cert_der): + cert = decoder.decode(cert_der, asn1Spec=rfc2459.Certificate())[0] + + params = {} + + # CA public key needs to be handled dynamically in the kit, so we can use fake data for now + params['ca_public_key'] = bin_to_c_hex(b'\x00' * 64) + + info = cert_sn_offset_length(cert) + params['cert_sn_cert_loc_offset'] = info['offset'] + params['cert_sn_cert_loc_count'] = info['length'] + if info['length'] < 1 or info['length'] > 20: + raise ValueError('Invalid certificate SN length (no more than 20 bytes).') + + not_before = set_time_params(params, cert, 'notBefore') + not_after = set_time_params(params, cert, 'notAfter') + + expire_years = not_after.year - not_before.year + if expire_years < 1 or expire_years > 31: + expire_years = 0 + params['expire_years'] = expire_years + + info = cert_signer_id_offset_length(cert, 'subject') + params['signer_id_cert_loc_offset'] = info['offset'] + params['signer_id_cert_loc_count'] = info['length'] + + info = cert_public_key_offset_length(cert) + params['public_key_cert_loc_offset'] = info['offset'] + params['public_key_cert_loc_count'] = info['length'] + + info = cert_subj_key_id_offset_length(cert) + if info is not None: + params['subj_key_id_cert_loc_offset'] = info['offset'] + params['subj_key_id_cert_loc_count'] = info['length'] + else: + params['subj_key_id_cert_loc_offset'] = 0 + params['subj_key_id_cert_loc_count'] = 0 + + info = cert_auth_key_id_offset_length(cert) + if info is not None: + params['auth_key_id_cert_loc_offset'] = info['offset'] + params['auth_key_id_cert_loc_count'] = info['length'] + else: + params['auth_key_id_cert_loc_offset'] = 0 + params['auth_key_id_cert_loc_count'] = 0 + + info = cert_tbs_offset_length(cert) + params['tbs_cert_loc_offset'] = info['offset'] + params['tbs_cert_loc_count'] = info['length'] + + info = cert_sig_offset_length(cert) + params['signature_cert_loc_offset'] = info['offset'] + params['signature_cert_loc_count'] = info['length'] + + params['cert_template'] = bin_to_c_hex(cert_der) + + return string.Template(cert_def_1_signer_c).substitute(params) + + +def gen_cert_def_c_device(cert_der): + cert = decoder.decode(cert_der, asn1Spec=rfc2459.Certificate())[0] + + params = {} + + info = cert_sn_offset_length(cert) + params['cert_sn_cert_loc_offset'] = info['offset'] + params['cert_sn_cert_loc_count'] = info['length'] + if info['length'] < 1 or info['length'] > 20: + raise ValueError('Invalid certificate SN length (no more than 20 bytes).') + + info = cert_signer_id_offset_length(cert, 'issuer') + params['signer_id_cert_loc_offset'] = info['offset'] + params['signer_id_cert_loc_count'] = info['length'] + + not_before = set_time_params(params, cert, 'notBefore') + not_after = set_time_params(params, cert, 'notAfter') + + expire_years = not_after.year - not_before.year + if expire_years < 1 or expire_years > 31: + expire_years = 0 + # Don't bother re-setting notAfter + params['expire_date_cert_loc_offset'] = 0 + params['expire_date_cert_loc_count'] = 0 + params['expire_years'] = expire_years + + info = cert_public_key_offset_length(cert) + params['public_key_cert_loc_offset'] = info['offset'] + params['public_key_cert_loc_count'] = info['length'] + + info = cert_subj_key_id_offset_length(cert) + if info is not None: + params['subj_key_id_cert_loc_offset'] = info['offset'] + params['subj_key_id_cert_loc_count'] = info['length'] + else: + params['subj_key_id_cert_loc_offset'] = 0 + params['subj_key_id_cert_loc_count'] = 0 + + info = cert_auth_key_id_offset_length(cert) + if info is not None: + params['auth_key_id_cert_loc_offset'] = info['offset'] + params['auth_key_id_cert_loc_count'] = info['length'] + else: + params['auth_key_id_cert_loc_offset'] = 0 + params['auth_key_id_cert_loc_count'] = 0 + + info = cert_tbs_offset_length(cert) + params['tbs_cert_loc_offset'] = info['offset'] + params['tbs_cert_loc_count'] = info['length'] + + info = cert_sig_offset_length(cert) + params['signature_cert_loc_offset'] = info['offset'] + params['signature_cert_loc_count'] = info['length'] + + params['cert_template'] = bin_to_c_hex(cert_der) + + return string.Template(cert_def_2_device_c).substitute(params) + + +def gen_cert_def_c_device_csr(csr_der): + # Use the device certificate to create a CSR template + csr = decoder.decode(csr_der, asn1Spec=rfc2314.CertificationRequest())[0] + + params = {} + + info = csr_public_key_offset_length(csr) + params['public_key_cert_loc_offset'] = info['offset'] + params['public_key_cert_loc_count'] = info['length'] + + info = cert_tbs_offset_length(csr) # cert TBS works for CSR too + params['tbs_cert_loc_offset'] = info['offset'] + params['tbs_cert_loc_count'] = info['length'] + + info = cert_sig_offset_length(csr) # cert sig works for CSR too + params['signature_cert_loc_offset'] = info['offset'] + params['signature_cert_loc_count'] = info['length'] + + params['cert_template'] = bin_to_c_hex(csr_der) + + return string.Template(cert_def_3_device_csr_c).substitute(params) + + +def esp_create_cert_def_str(cert_der, cert_type): + cert = decoder.decode(cert_der, asn1Spec=rfc2459.Certificate())[0] + params = {} + + info = cert_sn_offset_length(cert) + params['cert_sn_cert_loc_offset'] = info['offset'] + params['cert_sn_cert_loc_count'] = info['length'] + if info['length'] < 1 or info['length'] > 20: + raise ValueError('Invalid certificate SN length (no more than 20 bytes).') + + if cert_type == "SIGNER_CERT": + info = cert_signer_id_offset_length(cert, 'subject') + else: + info = cert_signer_id_offset_length(cert, 'issuer') + params['signer_id_cert_loc_offset'] = info['offset'] + params['signer_id_cert_loc_count'] = info['length'] + + not_before = set_time_params(params, cert, 'notBefore') + not_after = set_time_params(params, cert, 'notAfter') + + expire_years = not_after.year - not_before.year + if expire_years < 1 or expire_years > 31: + expire_years = 0 + # Don't bother re-setting notAfter + params['expire_date_cert_loc_offset'] = 0 + params['expire_date_cert_loc_count'] = 0 + params['expire_years'] = expire_years + + info = cert_public_key_offset_length(cert) + params['public_key_cert_loc_offset'] = info['offset'] + params['public_key_cert_loc_count'] = info['length'] + + info = cert_subj_key_id_offset_length(cert) + if info is not None: + params['subj_key_id_cert_loc_offset'] = info['offset'] + params['subj_key_id_cert_loc_count'] = info['length'] + else: + params['subj_key_id_cert_loc_offset'] = 0 + params['subj_key_id_cert_loc_count'] = 0 + + info = cert_auth_key_id_offset_length(cert) + if info is not None: + params['auth_key_id_cert_loc_offset'] = info['offset'] + params['auth_key_id_cert_loc_count'] = info['length'] + else: + params['auth_key_id_cert_loc_offset'] = 0 + params['auth_key_id_cert_loc_count'] = 0 + + info = cert_tbs_offset_length(cert) + params['tbs_cert_loc_offset'] = info['offset'] + params['tbs_cert_loc_count'] = info['length'] + + info = cert_sig_offset_length(cert) + params['signature_cert_loc_offset'] = info['offset'] + params['signature_cert_loc_count'] = info['length'] + + params['cert_template'] = bin_to_c_hex(cert_der) + + out_str = format(params['public_key_cert_loc_offset'], '04d') + out_str = out_str + format(params['public_key_cert_loc_count'], '04d') + + out_str = out_str + format(params['signature_cert_loc_offset'], '04d') + out_str = out_str + format(params['signature_cert_loc_count'], '04d') + + out_str = out_str + format(params['issue_date_cert_loc_offset'], '04d') + out_str = out_str + format(params['issue_date_cert_loc_count'], '04d') + + out_str = out_str + format(params['expire_date_cert_loc_offset'], '04d') + out_str = out_str + format(params['expire_date_cert_loc_count'], '04d') + + out_str = out_str + format(params['signer_id_cert_loc_offset'], '04d') + out_str = out_str + format(params['signer_id_cert_loc_count'], '04d') + + out_str = out_str + format(params['cert_sn_cert_loc_offset'], '04d') + out_str = out_str + format(params['cert_sn_cert_loc_count'], '04d') + + out_str = out_str + format(params['auth_key_id_cert_loc_offset'], '04d') + out_str = out_str + format(params['auth_key_id_cert_loc_count'], '04d') + + out_str = out_str + format(params['subj_key_id_cert_loc_offset'], '04d') + out_str = out_str + format(params['subj_key_id_cert_loc_count'], '04d') + + out_str = out_str + format(params['tbs_cert_loc_offset'], '04d') + out_str = out_str + format(params['tbs_cert_loc_count'], '04d') + + # creating a string of the template data to send it to the ESP32 over serial port + # ESP32 will convert it from string to respective format and then utilize the template + count = 0 + for i in range(len(params['cert_template']) - 2): + if((params['cert_template'][i] + params['cert_template'][i + 1]) == '0x'): + count = count + 1 + out_str = out_str + params['cert_template'][i + 2:i + 4] + return out_str + + +if __name__ == "__main__": + main() diff --git a/esp_cryptoauth_utility/helper_scripts/cert_sign.py b/esp_cryptoauth_utility/helper_scripts/cert_sign.py new file mode 100644 index 0000000..b84f954 --- /dev/null +++ b/esp_cryptoauth_utility/helper_scripts/cert_sign.py @@ -0,0 +1,119 @@ +#!/usr/bin/env python +# Copyright 2020 Espressif Systems (Shanghai) Co., Ltd. +# +# 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. +from cryptography import x509 +from cryptography.hazmat.backends import default_backend +from cryptography.hazmat.primitives import serialization +from cryptography.x509.oid import NameOID +from cryptography.hazmat.primitives import hashes +from builtins import str +from datetime import datetime +import binascii + +# Loads private key +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()) + + +# Loads certificate +def load_certificate(cert_file_path): + cert_file = open(cert_file_path, "rb") + ca_cert = cert_file.read() + cert_file.close() + return x509.load_pem_x509_certificate(ca_cert, default_backend()) + + +def encode_dates(nvb_time, expire_years): + enc_dates = bytearray(b'\x00' * 3) + enc_dates[0] = (enc_dates[0] & 0x07) | ((((nvb_time.year - 2000) & 0x1F) << 3) & 0xFF) + enc_dates[0] = (enc_dates[0] & 0xF8) | ((((nvb_time.month) & 0x0F) >> 1) & 0xFF) + enc_dates[1] = (enc_dates[1] & 0x7F) | ((((nvb_time.month) & 0x0F) << 7) & 0xFF) + enc_dates[1] = (enc_dates[1] & 0x83) | (((nvb_time.day & 0x1F) << 2) & 0xFF) + enc_dates[1] = (enc_dates[1] & 0xFC) | (((nvb_time.hour & 0x1F) >> 3) & 0xFF) + enc_dates[2] = (enc_dates[2] & 0x1F) | (((nvb_time.hour & 0x1F) << 5) & 0xFF) + enc_dates[2] = (enc_dates[2] & 0xE0) | ((expire_years & 0x1F) & 0xFF) + enc_dates = bytes(enc_dates) + return enc_dates + +def sign_csr(cert_sign_req, ca_cert, ca_privkey, device_sn, nva_years): + + csr = x509.load_pem_x509_csr(cert_sign_req, default_backend()) + + nvb_time = datetime.utcnow() + nvb_time = nvb_time.replace(minute=0,second=0) + nva_time = nvb_time + nva_time = nva_time.replace(year=nvb_time.year + nva_years) + expire_years = 0 + enc_dates = encode_dates(nvb_time, expire_years) + # SAH256 hash of the public key and encoded dates + digest = hashes.Hash(hashes.SHA256(), backend=default_backend()) + pub_nums = csr.public_key().public_numbers() + + + try: + pubkey = pub_nums.x.to_bytes(32, byteorder='big', signed=False) + pubkey += pub_nums.y.to_bytes(32, byteorder='big', signed=False) + except AttributeError: # In case of python2 above code block will raise AttributeError + pubkey = bytes(bytearray.fromhex(hex(pub_nums.x)[2:-1] + hex(pub_nums.y)[2:-1])) + + digest.update(pubkey) + digest.update(enc_dates) + raw_sn = bytearray(digest.finalize()[:16]) + raw_sn[0] = raw_sn[0] & 0x7F # Force MSB bit to 0 to ensure positive integer + raw_sn[0] = raw_sn[0] | 0x40 # Force next bit to 1 to ensure the integer won't be trimmed in ASN.1 DER encoding + try: + cert_sn = int.from_bytes(raw_sn, byteorder='big', signed=False) + except AttributeError: # In case of python2 above code block will raise AttributeError + cert_sn = int(binascii.hexlify(raw_sn), 16) + + dev_sn = str(device_sn.upper()) + + device_subject = x509.Name([ + x509.NameAttribute(NameOID.ORGANIZATION_NAME, ca_cert.subject.get_attributes_for_oid(NameOID.ORGANIZATION_NAME)[0].value), + x509.NameAttribute(NameOID.COMMON_NAME,dev_sn), + ]) + + device_cert = x509.CertificateBuilder().subject_name( + device_subject + ).issuer_name( + ca_cert.subject + ).public_key( + csr.public_key() + ).serial_number( + cert_sn + ).not_valid_before( + nvb_time + ).not_valid_after( + nva_time + ).add_extension( + x509.KeyUsage( + digital_signature=True, key_encipherment=True, content_commitment=True, + data_encipherment=False, key_agreement=False, encipher_only=False, decipher_only=False, key_cert_sign=False, crl_sign=False + ), + critical=True + ).add_extension( + x509.BasicConstraints(ca=False, path_length=None), + critical=True + ).add_extension( + x509.AuthorityKeyIdentifier.from_issuer_public_key(ca_privkey.public_key()), + critical=False + ).sign( + private_key=ca_privkey, + algorithm=hashes.SHA256(), + backend=default_backend() + ) + return device_cert.public_bytes(serialization.Encoding.PEM) diff --git a/esp_cryptoauth_utility/helper_scripts/manifest.py b/esp_cryptoauth_utility/helper_scripts/manifest.py new file mode 100644 index 0000000..8c6f386 --- /dev/null +++ b/esp_cryptoauth_utility/helper_scripts/manifest.py @@ -0,0 +1,308 @@ +#!/usr/bin/env python +# ########################################################################## +# (c) 2017 Microchip Technology Inc. and its subsidiaries. You may use this +# software and any derivatives exclusively with Microchip products. +# +# THIS SOFTWARE IS SUPPLIED BY MICROCHIP "AS IS". NO WARRANTIES, WHETHER +# EXPRESS, IMPLIED OR STATUTORY, APPLY TO THIS SOFTWARE, INCLUDING ANY IMPLIED +# WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY, AND FITNESS FOR A PARTICULAR +# PURPOSE, OR ITS INTERACTION WITH MICROCHIP PRODUCTS, COMBINATION WITH ANY +# OTHER PRODUCTS, OR USE IN ANY APPLICATION. +# +# IN NO EVENT WILL MICROCHIP BE LIABLE FOR ANY INDIRECT, SPECIAL, PUNITIVE, +# INCIDENTAL OR CONSEQUENTIAL LOSS, DAMAGE, COST OR EXPENSE OF ANY KIND +# WHATSOEVER RELATED TO THE SOFTWARE, HOWEVER CAUSED, EVEN IF MICROCHIP HAS BEEN +# ADVISED OF THE POSSIBILITY OR THE DAMAGES ARE FORESEEABLE. TO THE FULLEST +# EXTENT ALLOWED BY LAW, MICROCHIP'S TOTAL LIABILITY ON ALL CLAIMS IN ANY WAY +# RELATED TO THIS SOFTWARE WILL NOT EXCEED THE AMOUNT OF FEES, IF ANY, THAT YOU +# HAVE PAID DIRECTLY TO MICROCHIP FOR THIS SOFTWARE. +# +# MICROCHIP PROVIDES THIS SOFTWARE CONDITIONALLY UPON YOUR ACCEPTANCE OF THESE +# TERMS. +############################################################################# +# Copyright 2020 Espressif Systems (Shanghai) Co., Ltd. +# 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 unicodedata +import base64 +from base64 import urlsafe_b64encode +import json +import re +from cryptography.hazmat.primitives.asymmetric import ec +from cryptography.hazmat.primitives.asymmetric import utils as crypto_utils +from cryptography.hazmat.primitives import hashes +from cryptography.hazmat.primitives.serialization import Encoding, PublicFormat +from cryptography.utils import int_to_bytes +from cryptography import x509 +from jose import utils +from cryptography.hazmat.backends import default_backend +import sys +import os +from . import serial +from . import cert_sign + +def create_signed_entry(entry, log_key, jws_header): + """ + Converts the unsigned manifest entry into the signed manifest format (divided jws) + """ + jws_data = {'header': {'uniqueId': entry['uniqueId']}, 'protected': jws_header, + 'payload': jws_b64encode(json.dumps(entry).encode('ascii'))} + + tbs = jws_data['protected'] + '.' + jws_data['payload'] + + signature = log_key.sign(tbs.encode('ascii'), ec.ECDSA(hashes.SHA256())) + + r_int, s_int = crypto_utils.decode_dss_signature(signature) + + signature = int_to_bytes(r_int, 32) + int_to_bytes(s_int, 32) + + jws_data['signature'] = jws_b64encode(signature) + + return jws_data + + +def jws_b64encode(source): + """Simple helper function to remove base64 padding""" + return urlsafe_b64encode(source).decode('ascii').rstrip('=') + + +def get_common_name(name): + """ + Get the common name string from a distinguished name (RDNSequence) + """ + for attr in name: + if attr.oid == x509.oid.NameOID.COMMON_NAME: + return attr.value + return None + + +def make_valid_filename(s): + """ + Convert an arbitrary string into one that can be used in an ascii filename. + """ + if sys.version_info[0] <= 2: + s = str(s).decode('utf-8') + else: + s = str(s) + # Normalize unicode characters + s = unicodedata.normalize('NFKD', s).encode('ascii', 'ignore').decode('ascii') + # Remove non-word and non-whitespace characters + s = re.sub(r'[^\w\s-]', '', s).strip() + # Replace repeated whitespace with an underscore + s = re.sub(r'\s+', '_', s) + # Replace repeated dashes with a single dash + s = re.sub(r'-+', '-', s) + return s + + +def generate_manifest_file(esp, args, init_mfg): + + retval = init_mfg.exec_cmd(esp._port, "print-chip-info") + serial.esp_cmd_check_ok(retval, "print-chip-info") + # find index fo SN in string + index = retval[1]['Return'].find("Serial Number:") + index += len("Serial Number:") + serial_number_hex = retval[1]['Return'][index:] + serial_number_hex = serial_number_hex.strip() + serial_number_hex = serial_number_hex.replace(' ','') + print('Serial Number:') + print(serial_number_hex.upper()) + if args.print_atecc608_type is True: + # only print chip info and exit. + exit(0) + + print("Generating Manifest") + certs = [] + + retval = init_mfg.exec_cmd(esp._port, "get-tngtls-root-cert") + serial.esp_cmd_check_ok(retval, "get-tngtls-root-cert") + + index = retval[1]['Return'].find("Root Cert Len:") + index += len("Root Cert Len:") + root_cert_size = retval[1]['Return'][index:index + 3] + root_cert_size = int(root_cert_size) + root_cert_der = bytearray(root_cert_size) + index = retval[1]['Return'].find("Certificate:") + index += len("Certificate:") + root_cert_hex = retval[1]['Return'][index + 1:] + root_cert_hex = root_cert_hex.strip() + root_cert_der = bytearray.fromhex(root_cert_hex) + + root_cert = x509.load_der_x509_certificate(root_cert_der, default_backend()) + certs.insert(0, root_cert) + + print(get_common_name(root_cert.subject)) + print(root_cert.public_bytes(encoding=Encoding.PEM).decode('utf-8')) + + print('TNG Root Public Key:') + + print(root_cert.public_key().public_bytes( + format=PublicFormat.SubjectPublicKeyInfo, + encoding=Encoding.PEM + ).decode('utf-8')) + + retval = init_mfg.exec_cmd(esp._port, "get-tngtls-signer-cert") + serial.esp_cmd_check_ok(retval, "get-tngtls-signer-cert") + + index = retval[1]['Return'].find("Signer Cert Len:") + index += len("Signer Cert Len:") + signer_cert_size = retval[1]['Return'][index:index + 3] + signer_cert_size = int(signer_cert_size) + signer_cert_der = bytearray(signer_cert_size) + index = retval[1]['Return'].find("Certificate:") + index += len("Certificate:") + signer_cert_hex = retval[1]['Return'][index:] + signer_cert_hex = signer_cert_hex.strip() + signer_cert_der = bytearray.fromhex(signer_cert_hex) + + print('TNG Signer Certificate:') + signer_cert = x509.load_der_x509_certificate(signer_cert_der, default_backend()) + certs.insert(0, signer_cert) + + print(get_common_name(signer_cert.subject)) + print(signer_cert.public_bytes(encoding=Encoding.PEM).decode('utf-8')) + + print('TNG Signer Public Key:') + + print(signer_cert.public_key().public_bytes( + format=PublicFormat.SubjectPublicKeyInfo, + encoding=Encoding.PEM + ).decode('utf-8')) + + # Note that this is a simple cryptographic validation and does not check + # any of the actual certificate data (validity dates, extensions, names, + # etc...) + print('Validate Signer Certificate:') + root_cert.public_key().verify( + signature=signer_cert.signature, + data=signer_cert.tbs_certificate_bytes, + signature_algorithm=ec.ECDSA(signer_cert.signature_hash_algorithm) + ) + print('OK\n') + + print('TNG Device Certificate:') + + retval = init_mfg.exec_cmd(esp._port, "get-tngtls-device-cert") + serial.esp_cmd_check_ok(retval, "get-tngtls-device-cert") + + index = retval[1]['Return'].find("Device Cert Len:") + index += len("Device Cert Len:") + device_cert_size = retval[1]['Return'][index:index + 3] + device_cert_size = int(device_cert_size) + device_cert_der = bytearray(device_cert_size) + index = retval[1]['Return'].find("Certificate:") + index += len("Certificate:") + + device_cert_hex = retval[1]['Return'][index:] + device_cert_hex = device_cert_hex.strip() + device_cert_der = bytearray.fromhex(device_cert_hex) + device_cert = x509.load_der_x509_certificate(device_cert_der, default_backend()) + certs.insert(0, device_cert) + + print(get_common_name(device_cert.subject)) + device_cert_pem = device_cert.public_bytes(encoding=Encoding.PEM).decode('utf-8') + print(device_cert_pem) + print("Saving device cert to output_files") + with open("./output_files/device_cert.pem", "w+") as dev_cert_file: + dev_cert_file.write(device_cert_pem) + print('TNG Device Public Key:') + # Note that we could, of course, pull this from the device certificate above. + # However, this demonstrates the tng_atcacert_device_public_key() function. + + print(device_cert.public_key().public_bytes( + format=PublicFormat.SubjectPublicKeyInfo, + encoding=Encoding.PEM + ).decode('utf-8')) + + print('Validate Device Certificate:') + signer_cert.public_key().verify( + signature=device_cert.signature, + data=device_cert.tbs_certificate_bytes, + signature_algorithm=ec.ECDSA(device_cert.signature_hash_algorithm) + ) + print('OK\n') + + device_entry = { + 'version': 1, + 'model': 'ATECC608A', + 'partNumber': 'ATECC608A-TNGTLS', + 'manufacturer': { + 'organizationName': 'Microchip Technology Inc', + 'organizationalUnitName': 'Secure Products Group' + }, + 'provisioner': { + 'organizationName': 'Microchip Technology Inc', + 'organizationalUnitName': 'Secure Products Group' + }, + 'distributor': { + 'organizationName': 'Microchip Technology Inc', + 'organizationalUnitName': 'Microchip Direct' + } + } + + device_entry['provisioningTimestamp'] = device_cert.not_valid_before.strftime('%Y-%m-%dT%H:%M:%S.%f')[:-3] + 'Z' + + print(serial_number_hex) + device_entry['uniqueId'] = serial_number_hex + + device_entry['publicKeySet'] = { + 'keys': [ + { + 'kid': '0', + 'kty': 'EC', + 'crv': 'P-256', + 'x': None, + 'y': None, + 'x5c': [ + base64.b64encode(device_cert_der).decode('ascii'), + base64.b64encode(signer_cert_der).decode('ascii') + ] + }, + {'kid': '1', 'kty': 'EC', 'crv': 'P-256', 'x': None, 'y': None}, + {'kid': '2', 'kty': 'EC', 'crv': 'P-256', 'x': None, 'y': None}, + {'kid': '3', 'kty': 'EC', 'crv': 'P-256', 'x': None, 'y': None}, + {'kid': '4', 'kty': 'EC', 'crv': 'P-256', 'x': None, 'y': None} + ] + } + + for key in device_entry['publicKeySet']['keys']: + public_key = bytearray(64) + print('reading slot {} public key'.format(key['kid'])) + retval = init_mfg.exec_cmd(esp._port, "generate-pubkey {}".format(key['kid'])) + serial.esp_cmd_check_ok(retval, "pub-key-gen") + index = retval[1]['Return'].find("Public Key:") + index += len("Public Key:") + public_key_hex = retval[1]['Return'][index:] + public_key_hex = public_key_hex.strip() + public_key = bytearray.fromhex(public_key_hex) + key['x'] = utils.base64url_encode(public_key[0:32]).decode('ascii') + key['y'] = utils.base64url_encode(public_key[32:64]).decode('ascii') + + # If a logging key and certificate was provided create a manifest file + log_key = cert_sign.load_privatekey(args.signer_privkey) + log_cert = cert_sign.load_certificate(args.signer_cert) + # Generate the key and certificate ids for JWS + log_key_id = jws_b64encode(log_cert.extensions.get_extension_for_class(x509.SubjectKeyIdentifier).value.digest) + log_cert_id = jws_b64encode(log_cert.fingerprint(hashes.SHA256())) + + # Precompute the JWT header + jws_header = {'typ': 'JWT', 'alg': 'ES256', 'kid': log_key_id, 'x5t#S256': log_cert_id} + jws_header = jws_b64encode(json.dumps(jws_header).encode('ascii')) + + manifest = json.dumps([create_signed_entry(device_entry, log_key, jws_header)], indent=2).encode('ascii') + + filename = make_valid_filename(device_entry['uniqueId']) + '_manifest' + '.json' + os.getcwd() + with open('./output_files/' + filename, 'wb') as f: + f.write(manifest) + print('\n\nGenerated the manifest file ' + filename + ' in output_files') diff --git a/esp_cryptoauth_utility/helper_scripts/serial.py b/esp_cryptoauth_utility/helper_scripts/serial.py new file mode 100644 index 0000000..405434b --- /dev/null +++ b/esp_cryptoauth_utility/helper_scripts/serial.py @@ -0,0 +1,108 @@ +#!/usr/bin/env python +# Copyright 2020 Espressif Systems (Shanghai) Co., Ltd. +# +# 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 +from sys import exit +import time +import subprocess +import collections +import sys +try: + import esptool +except ImportError: # cheat and use IDF's copy of esptool if available + idf_path = os.getenv("IDF_PATH") + if not idf_path or not os.path.exists(idf_path): + raise + sys.path.insert(0, os.path.join(idf_path, "components", "esptool_py", "esptool")) + import esptool + + +class cmd_interpreter: + """ + This class is for is the command line interaction with the secure_cert_mfg firmware for manufacturing. + It executes the specified commands and returns its result. + It is a stateless, thus does not maintain the current state of the firmware. + """ + + def wait_for_init(self, port): + print("Wait for init") + port.timeout = 1.5 + port.baudrate = 115200 + start_time = time.time() + p_timeout = 20 + while True: + line = port.readline() + if b'Initialising Command line: >>' in line: + print("- CLI Initialised") + return True + elif (time.time() - start_time) > p_timeout: + print("connection timed out") + return False + + def exec_cmd(self, port, command, args=None): + ret = "" + status = None + port.timeout = 3 + port.baudrate = 115200 + port.write(command.encode() + b'\r') + if args: + time.sleep(0.1) + if type(args) is str: + args = args.encode() + port.write(args) + port.write(b'\0') + + while True: + port.timeout = 1.5 + line = (port.readline()).decode() + print(line) + if 'Status: Success' in line: + status = True + elif 'Status: Failure' in line: + status = False + if status is True or status is False: + while True: + line = (port.readline()).decode() + if ">>" in line: + if status is True: + print(line) + break + else: + ret += line + return [{"Status": status}, {"Return": ret}] + + +def _exec_shell_cmd(self, command): + result = subprocess.Popen((command).split(), stdout=subprocess.PIPE) + out, err = result.communicate() + return out + + +def load_app_stub(bin_path, esp): + esp.connect() + abs_bin_path = os.path.dirname(os.path.abspath(__file__)) + '/../' + bin_path + if (os.path.exists(abs_bin_path) is False): + print("Stub not found") + exit(0) + arg_tuple = collections.namedtuple('ram_image', ['filename']) + args = arg_tuple(abs_bin_path) + esp.change_baud(baud=921600) + esptool.load_ram(esp, args) + + +def esp_cmd_check_ok(retval, cmd_str): + if retval[0]['Status'] is not True: + print((cmd_str + "failed to execute")) + print((retval[1]['Return'])) + exit(0) diff --git a/esp_cryptoauth_utility/output_files/README.md b/esp_cryptoauth_utility/output_files/README.md new file mode 100644 index 0000000..f3946fa --- /dev/null +++ b/esp_cryptoauth_utility/output_files/README.md @@ -0,0 +1,2 @@ +# Output Files +After successfull execution of the scripts the certificates or manifest obtained will be stored here. diff --git a/esp_cryptoauth_utility/requirements.txt b/esp_cryptoauth_utility/requirements.txt new file mode 100644 index 0000000..80430f5 --- /dev/null +++ b/esp_cryptoauth_utility/requirements.txt @@ -0,0 +1,4 @@ +cryptography>=2.7 +pyasn1_modules==0.1.5 +pyasn1==0.3.7 +python-jose==3.1.0 diff --git a/esp_cryptoauth_utility/sample_bins/secure_cert_mfg.bin b/esp_cryptoauth_utility/sample_bins/secure_cert_mfg.bin new file mode 100644 index 0000000..2fda6bc Binary files /dev/null and b/esp_cryptoauth_utility/sample_bins/secure_cert_mfg.bin differ diff --git a/esp_cryptoauth_utility/secure_cert_mfg.py b/esp_cryptoauth_utility/secure_cert_mfg.py new file mode 100644 index 0000000..81a011e --- /dev/null +++ b/esp_cryptoauth_utility/secure_cert_mfg.py @@ -0,0 +1,203 @@ +#!/usr/bin/env python +# Copyright 2020 Espressif Systems (Shanghai) Co., Ltd. +# 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 argparse +from pyasn1_modules import pem +from sys import exit +import helper_scripts as hs +import binascii +import os +import sys +try: + import esptool +except ImportError: # cheat and use IDF's copy of esptool if available + idf_path = os.getenv("IDF_PATH") + if not idf_path or not os.path.exists(idf_path): + raise + sys.path.insert(0, os.path.join(idf_path, "components", "esptool_py", "esptool")) + import esptool + + +BINARY_STUB_PATH = '/sample_bins/secure_cert_mfg.bin' + + +def main(): + parser = argparse.ArgumentParser(description='''Provision the ESPWROOM32SE device with + device_certificate and signer_certificate required for TLS authentication''') + + parser.add_argument( + '--signer-cert', + dest='signer_cert', + default='sample_certs/sample_signer_cert.pem', + metavar='relative/path/to/signer_cert.pem', + help='relative path(from secure_cert_mfg.py) to signer certificate.') + + parser.add_argument( + '--signer-cert-private-key', + dest='signer_privkey', + default='sample_certs/sample_signer_key.pem', + metavar='relative/path/to/signer-priv-key', + help='relative path(from secure_cert_mfg.py) to signer certificate private key') + + parser.add_argument( + "--pwd", '--password', + dest='password', + metavar='[password]', + help='the password associated with the private key') + + parser.add_argument( + "--port", '-p', + dest='port', + metavar='[port]', + required=True, + help='uart com port to which ESP device is connected') + + parser.add_argument( + "--i2c-sda-pin', '-sda_pin'", + dest='i2c_sda_pin', + default=16,type=int, + help='The pin no of I2C SDA pin of esp32 to which atecc608 is connected, default = 16') + + parser.add_argument( + "--i2c-scl-pin', '-scl_pin'", + dest='i2c_scl_pin', + default=17,type=int, + help='The pin no of I2C SCL pin of esp32 to which atecc608 is connected, default = 17') + + parser.add_argument( + "--type", "--print-atecc608-type", + dest='print_atecc608_type',action='store_true', + help='print type of atecc608 chip connected to your ESP device') + + parser.add_argument( + "--valid-for-years", + dest='nva_years', + default=40,type=int, + help='number of years for which device cert is valid (from current year), efault = 40') + args = parser.parse_args() + esp = esptool.ESP32ROM(args.port,baud=115200) + hs.serial.load_app_stub(BINARY_STUB_PATH,esp) + init_mfg = hs.serial.cmd_interpreter() + + retval = init_mfg.wait_for_init(esp._port) + if retval is not True: + print("CMD prompt timed out.") + exit(0) + + retval = init_mfg.exec_cmd(esp._port, "init {0} {1}".format(args.i2c_sda_pin, args.i2c_scl_pin)) + hs.serial.esp_cmd_check_ok(retval, "init {0} {1}".format(args.i2c_sda_pin, args.i2c_scl_pin)) + + if "TrustCustom" in retval[1]['Return']: + print("ATECC608 chip is of type TrustCustom") + provision_trustcustom_device(esp, args,init_mfg) + elif "Trust&Go" in retval[1]['Return']: + print("ATECC608 chip is of type Trust&Go") + hs.manifest.generate_manifest_file(esp, args, init_mfg) + elif "TrustFlex" in retval[1]['Return']: + print("ATECC608 chip is of type TrustFlex") + hs.manifest.generate_manifest_file(esp, args, init_mfg) + else: + print("Invalid type") + exit(0) + + +def provision_trustcustom_device(esp, args, init_mfg): + + retval = init_mfg.exec_cmd(esp._port, "print-chip-info") + hs.serial.esp_cmd_check_ok(retval, "print-chip-info") + + index = retval[1]['Return'].find("Serial Number:\r\n") + index += len("Serial Number:\r\n") + serial_number = bytearray(9) + s = retval[1]['Return'][index:] + s = s.strip() + serial_number = bytearray.fromhex(s) + serial_number_hex = (binascii.hexlify(serial_number)).decode() + print('Serial Number:') + print(serial_number_hex.upper()) + + if args.print_atecc608_type is True: + # print chip info and exit + exit(0) + print("Provisioning the Device") + retval = init_mfg.exec_cmd(esp._port, "generate-keys 0") + hs.serial.esp_cmd_check_ok(retval, "generate-keys") + + retval = init_mfg.exec_cmd(esp._port, "generate-csr") + hs.serial.esp_cmd_check_ok(retval, "generate-csr") + + print("CSR obtained from device is:") + print(retval[1]['Return']) + + try: + # load private keys of signers to sign the CSR + private_key = hs.cert_sign.load_privatekey(args.signer_privkey, args.password) + signer_cert = hs.cert_sign.load_certificate(args.signer_cert) + # Sign the CSR using the generated keys + device_cert = hs.cert_sign.sign_csr(retval[1]['Return'].encode(), signer_cert, private_key, serial_number_hex, args.nva_years) + print("Device cert generated: \n") + dec_device_cert = device_cert.decode() + print(dec_device_cert) + print("Saving device cert to output_files/device_cert.pem") + + if esp_handle_file("./output_files/device_cert.pem", "write", dec_device_cert) is not True: + print("Error in writing device certificate") + exit(0) + cert_der = esp_handle_file("./output_files/device_cert.pem", "pem_read") + except ValueError: + print("Unsupported Key,Cert or CSR format specified.") + exit(0) + + # get the cert definition and template data in string format + print("program device cert") + cert_def_str = hs.cert2certdef.esp_create_cert_def_str(cert_der, "DEVICE_CERT") + + retval = init_mfg.exec_cmd(esp._port, "provide-cert-def 0", cert_def_str) + hs.serial.esp_cmd_check_ok(retval, "program-device-cert-def") + + retval = init_mfg.exec_cmd(esp._port, "program-dev-cert", device_cert) + hs.serial.esp_cmd_check_ok(retval, "program-dev-cert") + print(retval[1]['Return']) + + signer_cert_data = esp_handle_file(args.signer_cert, "read") + cert_der = esp_handle_file(args.signer_cert, "pem_read") + print("Signer cert is:") + print(signer_cert_data) + + print("program signer cert") + cert_def_str = hs.cert2certdef.esp_create_cert_def_str(cert_der, "SIGNER_CERT") + + retval = init_mfg.exec_cmd(esp._port, "provide-cert-def 1", cert_def_str) + hs.serial.esp_cmd_check_ok(retval, "program-signer-cert-def") + + retval = init_mfg.exec_cmd(esp._port, "program-signer-cert", signer_cert_data) + hs.serial.esp_cmd_check_ok(retval, "program-signer-cert") + + +def esp_handle_file(file_name, operation, data=None): + if operation == "read": + with open(file_name, "r") as cert_file: + data = cert_file.read() + return data + elif operation == "pem_read": + with open(file_name, "r") as cert_file: + data = pem.readPemFromFile(cert_file) + return data + elif operation == "write": + with open(file_name, "w+") as cert_file: + cert_file.write(data) + return True + + +if __name__ == "__main__": + main() diff --git a/idf_component.yml b/idf_component.yml new file mode 100644 index 0000000..7e512cd --- /dev/null +++ b/idf_component.yml @@ -0,0 +1,6 @@ +version: "3.3.1" +description: "esp-cryptoauthlib: The port of Microchip CryptoAuthentication Library for ESP-IDF" +url: https://github.com/espressif/esp-cryptoauthlib +dependencies: + idf: + version: ">=4.3" diff --git a/port/atca_cfgs_port.c b/port/atca_cfgs_port.c new file mode 100644 index 0000000..e591a0a --- /dev/null +++ b/port/atca_cfgs_port.c @@ -0,0 +1,54 @@ +/** + * \file + * \brief a set of default configurations for various ATCA devices and interfaces + * + * \copyright (c) 2015-2018 Microchip Technology Inc. and its subsidiaries. + * + * \page License + * + * Subject to your compliance with these terms, you may use Microchip software + * and any derivatives exclusively with Microchip products. It is your + * responsibility to comply with third party license terms applicable to your + * use of third party software (including open source software) that may + * accompany Microchip software. + * + * THIS SOFTWARE IS SUPPLIED BY MICROCHIP "AS IS". NO WARRANTIES, WHETHER + * EXPRESS, IMPLIED OR STATUTORY, APPLY TO THIS SOFTWARE, INCLUDING ANY IMPLIED + * WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY, AND FITNESS FOR A + * PARTICULAR PURPOSE. IN NO EVENT WILL MICROCHIP BE LIABLE FOR ANY INDIRECT, + * SPECIAL, PUNITIVE, INCIDENTAL OR CONSEQUENTIAL LOSS, DAMAGE, COST OR EXPENSE + * OF ANY KIND WHATSOEVER RELATED TO THE SOFTWARE, HOWEVER CAUSED, EVEN IF + * MICROCHIP HAS BEEN ADVISED OF THE POSSIBILITY OR THE DAMAGES ARE + * FORESEEABLE. TO THE FULLEST EXTENT ALLOWED BY LAW, MICROCHIP'S TOTAL + * LIABILITY ON ALL CLAIMS IN ANY WAY RELATED TO THIS SOFTWARE WILL NOT EXCEED + * THE AMOUNT OF FEES, IF ANY, THAT YOU HAVE PAID DIRECTLY TO MICROCHIP FOR + * THIS SOFTWARE. + */ + +#include +#include "atca_cfgs.h" +#include "atca_iface.h" +#include "atca_device.h" + +/** \defgroup config Configuration (cfg_) + * \brief Logical device configurations describe the CryptoAuth device type and logical interface. + @{ */ + +/* if the number of these configurations grows large, we can #ifdef them based on required device support */ + +/** \brief default configuration for an ECCx08A device */ +ATCAIfaceCfg cfg_ateccx08a_i2c_default = { + .iface_type = ATCA_I2C_IFACE, + .devtype = ATECC608A, +#ifdef ATCA_ENABLE_DEPRECATED + .atcai2c.slave_address = 0x6A, +#else + .atcai2c.address = 0x6A, +#endif + .atcai2c.bus = 0, + .atcai2c.baud = 100000, + .wake_delay = CONFIG_I2C_MANAGER_0_TIMEOUT, + .rx_retries = 20 +}; + +/** @} */ diff --git a/port/atca_config.h b/port/atca_config.h new file mode 100644 index 0000000..c3b8adf --- /dev/null +++ b/port/atca_config.h @@ -0,0 +1,35 @@ +#pragma once +/* Cryptoauthlib Configuration File */ +#ifndef ATCA_CONFIG_H +#define ATCA_CONFIG_H + +/* Include HALS */ +#define ATCA_HAL_I2C +#define ATCA_USE_RTOS_TIMER 1 +#define ATCA_MBEDTLS +//#define ATCA_CA_SUPPORT +/* Included device support */ +#define ATCA_ATECC608_SUPPORT + +#define ATCA_TNG_LEGACY_SUPPORT +#define ATCA_TFLEX_SUPPORT +#define ATCA_TNGTLS_SUPPORT +#define ATCA_TNGLORA_SUPPORT + +/* \brief How long to wait after an initial wake failure for the POST to + * complete. + * If Power-on self test (POST) is enabled, the self test will run on waking + * from sleep or during power-on, which delays the wake reply. + */ +#ifndef ATCA_POST_DELAY_MSEC +#define ATCA_POST_DELAY_MSEC 25 +#endif + +#define ATCA_PLATFORM_MALLOC malloc +#define ATCA_PLATFORM_FREE free + +#define ATCA_PRINTF +#endif // ATCA_CONFIG_H + +#define atca_delay_ms hal_delay_ms +#define atca_delay_us hal_delay_us \ No newline at end of file diff --git a/port/hal_core2foraws_i2c.c b/port/hal_core2foraws_i2c.c new file mode 100644 index 0000000..23be0eb --- /dev/null +++ b/port/hal_core2foraws_i2c.c @@ -0,0 +1,159 @@ +/* + * Copyright 2018 Espressif Systems (Shanghai) PTE LTD + * 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. + */ + +#include +#include +#include +#include "esp_err.h" +#include "esp_log.h" +#include "cryptoauthlib.h" +#include "i2c_manager.h" +#include "core2foraws_common.h" + +const char* TAG = "ATECC608_HAL"; + +ATCA_STATUS status; + +/** \brief method to change the bus speec of I2C + * \param[in] iface interface on which to change bus speed + * \param[in] speed baud rate (typically 100000 or 400000) + */ +ATCA_STATUS hal_i2c_change_baud(ATCAIface iface, uint32_t speed) +{ + //ESP_LOGD(TAG, "Baudrate Changed"); + return ATCA_SUCCESS; +} + +/** \brief + - this HAL implementation assumes you've included the START Twi libraries in your project, otherwise, + the HAL layer will not compile because the START TWI drivers are a dependency * + */ + +/** \brief hal_i2c_init manages requests to initialize a physical interface. it manages use counts so when an interface + * has released the physical layer, it will disable the interface for some other use. + * You can have multiple ATCAIFace instances using the same bus, and you can have multiple ATCAIFace instances on + * multiple i2c buses, so hal_i2c_init manages these things and ATCAIFace is abstracted from the physical details. + */ + +/** \brief initialize an I2C interface using given config + * \param[in] hal - opaque ptr to HAL data + * \param[in] cfg - interface configuration + * \return ATCA_SUCCESS on success, otherwise an error code. + */ +ATCA_STATUS hal_i2c_init( ATCAIface iface, ATCAIfaceCfg *cfg ) +{ + // ESP_LOGE( TAG, "ATECC608 ADDRESS: 0x%x", cfg->atcai2c.address ); + esp_err_t err = i2c_manager_init( COMMON_I2C_INTERNAL ); + if (err != ESP_OK) { + return ATCA_COMM_FAIL; + } else { + return ATCA_SUCCESS; + } +} + +/** \brief HAL implementation of I2C post init + * \param[in] iface instance + * \return ATCA_SUCCESS + */ +ATCA_STATUS hal_i2c_post_init( ATCAIface iface ) +{ + return ATCA_SUCCESS; +} + +/** \brief HAL implementation of I2C send + * \param[in] iface instance + * \param[in] word_address device transaction type + * \param[in] txdata pointer to space to bytes to send + * \param[in] txlength number of bytes to send + * \return ATCA_SUCCESS on success, otherwise an error code. + */ +ATCA_STATUS hal_i2c_send( ATCAIface iface, uint8_t address, uint8_t *txdata, int txlength ) +{ + esp_err_t err = ESP_FAIL; + // ESP_LOGE( TAG, "SEND ATECC608 ADDRESS: 0x%x", address ); + err = i2c_manager_write( COMMON_I2C_INTERNAL, address >> 1, I2C_NO_REG, txdata, txlength ); + + ESP_LOGD( TAG, "txdata: %p , txlength: %d error: %s", txdata, txlength, esp_err_to_name( err ) ); + + if ( err == ESP_OK) + { + return ATCA_SUCCESS; + } + + return ATCA_COMM_FAIL; +} + +/** \brief HAL implementation of I2C receive function + * \param[in] iface Device to interact with. + * \param[in] address Device address + * \param[out] rxdata Data received will be returned here. + * \param[in,out] rxlength As input, the size of the rxdata buffer. + * As output, the number of bytes received. + * \return ATCA_SUCCESS on success, otherwise an error code. + */ +ATCA_STATUS hal_i2c_receive( ATCAIface iface, uint8_t address, uint8_t *rxdata, uint16_t *rxlength ) +{ + esp_err_t err = ESP_FAIL; + // ESP_LOGE( TAG, "RECEIVE ATECC608 ADDRESS: 0x%x", address ); + if ( ( NULL == rxlength ) || ( NULL == rxdata ) ) + { + return ATCA_TRACE( ATCA_INVALID_POINTER, "NULL pointer encountered" ); + } + + err = i2c_manager_read( COMMON_I2C_INTERNAL, address >> 1, I2C_NO_REG, rxdata, *rxlength ); + + if ( err == ESP_OK ) + { + return ATCA_SUCCESS; + } + + return ATCA_COMM_FAIL; +} + +/** \brief manages reference count on given bus and releases resource if no more refences exist + * \param[in] hal_data - opaque pointer to hal data structure - known only to the HAL implementation + * \return ATCA_SUCCESS on success, otherwise an error code. + */ +ATCA_STATUS hal_i2c_release( void *hal_data ) +{ + // i2c_free_device(atecc608_device); + return ATCA_SUCCESS; +} + +/** \brief Perform control operations for the kit protocol + * \param[in] iface Interface to interact with. + * \param[in] option Control parameter identifier + * \param[in] param Optional pointer to parameter value + * \param[in] paramlen Length of the parameter + * \return ATCA_SUCCESS on success, otherwise an error code. + */ +ATCA_STATUS hal_i2c_control( ATCAIface iface, uint8_t option, void *param, size_t paramlen ) +{ + ( void )param; + ( void )paramlen; + + if ( iface && iface->mIfaceCFG ) + { + if ( ATCA_HAL_CHANGE_BAUD == option ) + { + return hal_i2c_change_baud( iface, *( uint32_t * )param ); + } + else + { + return ATCA_UNIMPLEMENTED; + } + } + return ATCA_BAD_PARAM; +} \ No newline at end of file diff --git a/port/include/FreeRTOS.h b/port/include/FreeRTOS.h new file mode 100644 index 0000000..fbb3d1a --- /dev/null +++ b/port/include/FreeRTOS.h @@ -0,0 +1 @@ +#include_next diff --git a/port/include/semphr.h b/port/include/semphr.h new file mode 100644 index 0000000..1f525bb --- /dev/null +++ b/port/include/semphr.h @@ -0,0 +1 @@ +#include_next diff --git a/port/include/task.h b/port/include/task.h new file mode 100644 index 0000000..d8827cb --- /dev/null +++ b/port/include/task.h @@ -0,0 +1 @@ +#include_next