feat: add example to re-enable jtag using hmac peripheral

This commit is contained in:
nilesh.kale 2023-10-25 15:15:42 +05:30
parent acb733845f
commit 37adea9a14
16 changed files with 550 additions and 0 deletions

View File

@ -232,6 +232,10 @@ JTAG with Flash Encryption or Secure Boot
By default, enabling Flash Encryption and/or Secure Boot will disable JTAG debugging. On first boot, the bootloader will burn an eFuse bit to permanently disable JTAG at the same time it enables the other features.
.. only:: SOC_HMAC_SUPPORTED
Please note that once JTAG is permanently disabled, it cannot be re-enabled for JTAG access. However, we do have the option of disabling JTAG softly. For more details on soft disabling and re-enabling soft-disabled JTAG, please refer to the :ref:`hmac_for_enabling_jtag`.
The project configuration option :ref:`CONFIG_SECURE_BOOT_ALLOW_JTAG` will keep JTAG enabled at this time, removing all physical security but allowing debugging. (Although the name suggests Secure Boot, this option can be applied even when only Flash Encryption is enabled).
However, OpenOCD may attempt to automatically read and write the flash in order to set :ref:`software breakpoints <jtag-debugging-tip-where-breakpoints>`. This has two problems:

View File

@ -132,6 +132,8 @@ JTAG enables
2. Pass this key value when calling the :cpp:func:`esp_hmac_jtag_enable` function from the firmware.
3. To re-disable JTAG in the firmware, reset the system or call :cpp:func:`esp_hmac_jtag_disable`.
End-to-end example of soft disable and re-enable JTAG workflow: :example:`security/hmac_soft_jtag`
For more details, see **{IDF_TARGET_NAME} Technical Reference Manual** > **HMAC Accelerator (HMAC)** [`PDF <{IDF_TARGET_TRM_EN_URL}#hmac>`__].

View File

@ -6,6 +6,17 @@ examples/security/flash_encryption:
temporary: true
reason: lack of runners
examples/security/hmac_soft_jtag:
disable:
- if: SOC_HMAC_SUPPORTED != 1
disable_test:
- if: IDF_TARGET not in ["esp32c6"]
reason: sufficient to test on one HMAC-capable chip
depends_components:
- esp_hw_support
depends_filepatterns:
- examples/security/hmac_soft_jtag/**/*
examples/security/nvs_encryption_hmac:
disable:
- if: SOC_HMAC_SUPPORTED != 1

View File

@ -0,0 +1,8 @@
# For more information about build system see
# https://docs.espressif.com/projects/esp-idf/en/latest/api-guides/build-system.html
# The following five lines of boilerplate have to be in your project's
# CMakeLists in this exact order for cmake to work correctly
cmake_minimum_required(VERSION 3.16)
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
project(hmac_soft_jtag)

View File

@ -0,0 +1,113 @@
| Supported Targets | ESP32-C3 | ESP32-C6 | ESP32-H2 | ESP32-P4 | ESP32-S2 | ESP32-S3 |
| ----------------- | -------- | -------- | -------- | -------- | -------- | -------- |
# JTAG Re-enable Example
This example showcases the use of HMAC peripheral for enabling the soft-disabled JTAG interface.
## How to use example
### Hardware Required
This example can be executed on any development board with a supported Espressif SOC chip - possessing a built-in HMAC peripheral (see `Supported Targets` table above).
### Configure the device
To configure the device for JTAG access, follow these steps:
**Note:** Before running the jtag_example_helper.py script, make sure to install the required dependencies by running the following command:
```bash
pip install -r requirements.txt
```
**Step 1:** Check JTAG status.
```bash
python jtag_example_helper.py check_jtag_status
```
If device is soft disabled, this example can re-enable it using further steps.
**Step 2:** Generate a 32 bytes HMAC key.
```bash
python jtag_example_helper.py generate_hmac_key <KEY_FILE>.bin
```
This generates a new 32-byte random HMAC key and store it in given file.
**Step 3:** Run the following command to burn the eFuse with the generated HMAC key with appropriate purpose. You can use purpose either HMAC_DOWN_ALL or HMAC_DOWN_JTAG. Check efuse summary to identify an available empty key block.
```bash
espefuse.py -p $ESPPORT burn_key <KEY_BLOCK_NO> <KEY_FILE>.bin HMAC_DOWN_ALL
```
**Step 4:** Generate token data from the HMAC key. Keep this token data handy before re-enabling JTAG access.
```bash
python jtag_example_helper.py generate_token <KEY_FILE>.bin
```
### Configure the project
Before the project configuration and build, be sure to set the correct chip target using `idf.py set-target <chip_name>`.
Key block to be used for re-enabling JTAG can be configured in the project configuration menu, under ``Example Configuration`` > ``key block to be used``. The default value is -1, indicating that the example will use the first found keys with the purpose either HMAC_DOWN_ALL or HMAC_DOWN_JTAG.
### Build and Flash
Build the project and flash it to the board, then run the monitor tool to view the serial output:
```bash
idf.py -p PORT flash monitor
```
It will open console to enter command. Refer [Re-enable & Disable JTAG](#Re-enable-&-Disable-JTAG) to know more about usage of example.
(To exit the serial monitor, type `Ctrl-]`.)
See the Getting Started Guide for full steps to configure and use ESP-IDF to build projects.
### Re-enable & Disable JTAG
#### Re-enable JTAG
This re-enables JTAG access until the next reboot or until disabled using this example. For disabling JTAG, refer [Disable JTAG](#Disable-JTAG)
**Note:** Even upon successful return, JTAG will only be enabled with a valid token_data.
```bash
enable_jtag <token_data>
```
Console logs while re-enabling JTAG:
```bash
I (314) main_task: Calling app_main()
Type 'help' to get the list of commands.
Use UP/DOWN arrows to navigate through command history.
Press TAB when typing command name to auto-complete.
I (394) main_task: Returned from app_main()
esp32c6> enable_jtag b2a49b1cce1be922bb7e431277413e3e8e6c3e8e6e17625c50ac66a9a857949b
I (10974) jtag_re_enable: Device is ready to re-enable.
I (10974) jtag_re_enable: Using HMAC key at block 8 with purpose HMAC_DOWN_JTAG
I (10984) jtag: JTAG re-enablement workflow performed, please check the JTAG connection manually
esp32c6>
```
#### Disable JTAG
This disables the temporarily enabled JTAG access.
```bash
disable_jtag
```
Console logs while disabling JTAG:
```bash
esp32c6> disable_jtag
I (25104) jtag_re_enable: JTAG disabled temporarily
esp32c6>
```

View File

@ -0,0 +1,86 @@
# SPDX-FileCopyrightText: 2023-2024 Espressif Systems (Shanghai) CO LTD
# SPDX-License-Identifier: Unlicense OR CC0-1.0
import argparse
import binascii
import hashlib
import hmac
import os
import subprocess
def generate_token_data(hmac_key_file: str) -> None:
with open(hmac_key_file, 'rb') as file:
key_data = file.read()
data = bytes([0] * 32)
token_data = hmac.HMAC(key_data, data, hashlib.sha256).digest()
token_hex = binascii.hexlify(token_data).decode('utf-8')
print(token_hex)
def generate_hmac_key(hmac_key_file: str) -> None:
hmac_key = os.urandom(32)
with open(hmac_key_file, 'wb') as file:
file.write(hmac_key)
def check_jtag_status() -> None:
esp_port = os.getenv('ESPPORT')
if not esp_port:
raise RuntimeError('ESPPORT not specified')
output = subprocess.check_output(['espefuse.py', 'summary']).decode('utf-8')
# check if JTAG is permenently/hard disabled
if ('DIS_PAD_JTAG' in output and 'JTAG = True' in output) or ('HARD_DIS_JTAG' in output and 'JTAG = True' in output):
print('JTAG functionality is permanently disabled')
else:
print('JTAG functionality is not permanently disabled')
# check if JTAG is software disabled
soft_dis_value = None
lines = output.split('\n')
for line in lines:
if 'SOFT_DIS_JTAG' in line:
hex_value = line.split('=')[-1].split(' ')[1] # Extract the hexadecimal part
soft_dis_value = int(hex_value, 16)
break
if soft_dis_value is not None:
# Count the number of 1's in the binary representation of soft_dis_value
ones_count = bin(soft_dis_value).count('1')
if ones_count % 2 != 0:
print('JTAG is software disabled')
else:
print('JTAG is not software disabled')
else:
print('SOFT_DIS_JTAG value not found in the output')
print('If JTAG is permenently disabled, it cannot be re-enabled.\nThis example re-enables only software disabled JTAG access')
def main() -> None:
parser = argparse.ArgumentParser()
subparsers = parser.add_subparsers(dest='command')
subparsers.add_parser('check_jtag_status', help='Check JTAG current status')
hmac_generator_parser = subparsers.add_parser('generate_hmac_key')
hmac_generator_parser.add_argument('hmac_key_file', help='File to store generated HMAC key')
token_generator_parser = subparsers.add_parser('generate_token')
token_generator_parser.add_argument('hmac_key_file', help='File containing the HMAC key')
args = parser.parse_args()
if args.command == 'check_jtag_status':
check_jtag_status()
elif args.command == 'generate_hmac_key':
generate_hmac_key(args.hmac_key_file)
elif args.command == 'generate_token':
generate_token_data(args.hmac_key_file)
else:
parser.print_help()
if __name__ == '__main__':
main()

View File

@ -0,0 +1,2 @@
idf_component_register(SRCS "example_main.c" "jtag_commands.c"
PRIV_INCLUDE_DIRS ".")

View File

@ -0,0 +1,13 @@
menu "Example Configuration"
config EXAMPLE_JTAG_SEC_HMAC_EFUSE_KEY_ID
int "eFuse key ID storing the HMAC key"
range -1 5
default -1
help
The eFuse block key ID stores the HMAC key necessary for deriving token data to enable JTAG access.
Note: If set to -1, the system will attempt to use the first found keys with the purpose
either HMAC_DOWN_ALL or HMAC_DOWN_JTAG.
endmenu

View File

@ -0,0 +1,43 @@
/*
* SPDX-FileCopyrightText: 2023-2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Unlicense OR CC0-1.0
*/
#include <stdio.h>
#include <string.h>
#include "esp_console.h"
#include "nvs_flash.h"
#include "jtag_commands.h"
#define PROMPT_STR CONFIG_IDF_TARGET
void app_main(void)
{
esp_console_repl_t *repl = NULL;
esp_console_repl_config_t repl_config = ESP_CONSOLE_REPL_CONFIG_DEFAULT();
repl_config.prompt = PROMPT_STR ">";
repl_config.max_cmdline_length = 256;
/* Register commands */
esp_console_register_help_command();
register_jtag_commands();
#if defined(CONFIG_ESP_CONSOLE_UART_DEFAULT) || defined(CONFIG_ESP_CONSOLE_UART_CUSTOM)
esp_console_dev_uart_config_t hw_config = ESP_CONSOLE_DEV_UART_CONFIG_DEFAULT();
ESP_ERROR_CHECK(esp_console_new_repl_uart(&hw_config, &repl_config, &repl));
#elif defined(CONFIG_ESP_CONSOLE_USB_CDC)
esp_console_dev_usb_cdc_config_t hw_config = ESP_CONSOLE_DEV_CDC_CONFIG_DEFAULT();
ESP_ERROR_CHECK(esp_console_new_repl_usb_cdc(&hw_config, &repl_config, &repl));
#elif defined(CONFIG_ESP_CONSOLE_USB_SERIAL_JTAG)
esp_console_dev_usb_serial_jtag_config_t hw_config = ESP_CONSOLE_DEV_USB_SERIAL_JTAG_CONFIG_DEFAULT();
ESP_ERROR_CHECK(esp_console_new_repl_usb_serial_jtag(&hw_config, &repl_config, &repl));
#else
#error Unsupported console type
#endif
ESP_ERROR_CHECK(esp_console_start_repl(repl));
}

View File

@ -0,0 +1,204 @@
/*
* SPDX-FileCopyrightText: 2023-2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Unlicense OR CC0-1.0
*/
#include <stdio.h>
#include <string.h>
#include <time.h>
#include <ctype.h>
#include "esp_efuse.h"
#include "esp_efuse_table.h"
#include "esp_log.h"
#include "esp_hmac.h"
#include "mbedtls/md.h"
#include "esp_console.h"
#include "argtable3/argtable3.h"
#define HMAC_KEY_SIZE 32
#define TOKEN_DATA_SIZE 32
static const char* TAG = "jtag_re_enable";
typedef struct {
struct arg_str *token_data;
struct arg_end *end;
} command_args_t;
static command_args_t command_args;
static bool is_hexadecimal(const char *str)
{
if (str == NULL || *str == '\0') {
return false;
}
while (*str != '\0') {
if (!isxdigit((unsigned char)*str)) {
return false;
}
str++;
}
return true;
}
const char *esp_efuse_purpose_str(esp_efuse_purpose_t purpose)
{
switch (purpose) {
case ESP_EFUSE_KEY_PURPOSE_HMAC_DOWN_ALL:
return "HMAC_DOWN_ALL";
case ESP_EFUSE_KEY_PURPOSE_HMAC_DOWN_JTAG:
return "HMAC_DOWN_JTAG";
default:
return "Unknown";
}
}
static esp_err_t is_device_ready_to_re_enable(void)
{
bool flag_jtag_hard_dis;
#if SOC_EFUSE_HARD_DIS_JTAG
flag_jtag_hard_dis = esp_efuse_read_field_bit(ESP_EFUSE_HARD_DIS_JTAG);
#else
flag_jtag_hard_dis = esp_efuse_read_field_bit(ESP_EFUSE_DIS_PAD_JTAG);
#endif
if (flag_jtag_hard_dis) {
ESP_LOGE(TAG, "JTAG permenently disabled. Can't re-enable.");
return ESP_FAIL;
}
size_t out_cnt = 0;
if (ESP_OK != esp_efuse_read_field_cnt(ESP_EFUSE_SOFT_DIS_JTAG, &out_cnt)) {
ESP_LOGE(TAG, "Error obtaining value for SOFT_DIS_JTAG");
return ESP_FAIL;
}
if (out_cnt == 0) {
ESP_LOGI(TAG, "JTAG soft disable efuse bit is not programmed, hence JTAG is already enabled");
return ESP_FAIL;
}
return ESP_OK;
}
static esp_err_t re_enable_jtag_utility(uint8_t token_data[])
{
esp_err_t status;
// Read the configured key block number from Kconfig
int configured_block_number = CONFIG_EXAMPLE_JTAG_SEC_HMAC_EFUSE_KEY_ID;
if (configured_block_number != -1) {
// User has configured a specific key block number
ESP_LOGI(TAG, "Using user-configured key block number %d for authentication", configured_block_number);
status = esp_hmac_jtag_enable(configured_block_number, token_data);
} else {
esp_efuse_purpose_t purposes[] = {
ESP_EFUSE_KEY_PURPOSE_HMAC_DOWN_ALL,
ESP_EFUSE_KEY_PURPOSE_HMAC_DOWN_JTAG
};
// Check if there is already a key block with the desired purpose
esp_efuse_block_t block;
status = ESP_FAIL;
for (unsigned i = 0; i < sizeof(purposes) / sizeof(esp_efuse_purpose_t); i++) {
if (esp_efuse_find_purpose(purposes[i], &block)) {
// key block with appropriate purpose found
ESP_LOGI(TAG, "Using HMAC key at block %d with purpose %s for authentication", block - (int)EFUSE_BLK4, esp_efuse_purpose_str(purposes[i]));
status = status && esp_hmac_jtag_enable(block - (int)EFUSE_BLK4, token_data);
}
}
if (EFUSE_BLK_KEY_MAX == block) {
ESP_LOGI(TAG, "HMAC key is not burned with required purpose. Please refer to device configuration in example readme for more details.");
return ESP_FAIL;
}
}
if (ESP_OK != status) {
ESP_LOGI(TAG, "Error in re-enabling JTAG");
return ESP_FAIL;
}
return ESP_OK;
}
static esp_err_t re_enable_jtag(int argc, char **argv)
{
int nerrors = arg_parse(argc, argv, (void **) &command_args);
if (nerrors != 0) {
arg_print_errors(stderr, command_args.end, argv[0]);
return ESP_FAIL;
}
if (ESP_OK != is_device_ready_to_re_enable()) {
return ESP_FAIL;
}
ESP_LOGI(TAG, "Device is ready to re-enable.");
const char* arg_token_data_str = command_args.token_data->sval[0];
if (strlen(arg_token_data_str) != TOKEN_DATA_SIZE * 2 || !is_hexadecimal(arg_token_data_str)) {
ESP_LOGE(TAG, "Invalid token_data. The token data should be 64 hexadecimal characters.");
return ESP_FAIL;
}
// Convert the string to uint8_t array
uint8_t token_data[TOKEN_DATA_SIZE];
for (int i = 0; i < TOKEN_DATA_SIZE; ++i) {
sscanf(arg_token_data_str + 2 * i, "%2hhx", &token_data[i]);
}
if (ESP_OK != re_enable_jtag_utility(token_data)) {
ESP_LOGI(TAG, "JTAG re-enabling failed");
return ESP_FAIL;
}
ESP_LOGI("jtag", "JTAG re-enablement workflow performed, please check the JTAG connection manually");
return ESP_OK;
}
static void register_enable_jtag(void)
{
command_args.token_data = arg_str1(NULL, NULL, "<token_data>", "token_data");
command_args.end = arg_end(1);
const esp_console_cmd_t cmd = {
.command = "enable_jtag",
.help = "Re-enables software JTAG access.\n\t Usage: enable_jtag <token_data>",
.hint = NULL,
.func = &re_enable_jtag,
.argtable = &command_args,
};
ESP_ERROR_CHECK(esp_console_cmd_register(&cmd));
}
static esp_err_t disable_jtag(int argc, char **argv)
{
if (ESP_OK != esp_hmac_jtag_disable()) {
ESP_LOGE(TAG, "Failed to disable JTAG");
return ESP_FAIL;
}
ESP_LOGI(TAG, "JTAG disabled temporarily");
return ESP_OK;
}
static void register_disable_jtag(void)
{
const esp_console_cmd_t cmd = {
.command = "disable_jtag",
.help = "Disables software JTAG access",
.hint = NULL,
.func = &disable_jtag,
};
ESP_ERROR_CHECK(esp_console_cmd_register(&cmd));
}
void register_jtag_commands(void)
{
register_enable_jtag();
register_disable_jtag();
}

View File

@ -0,0 +1,8 @@
/*
* SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Unlicense OR CC0-1.0
*/
#pragma once
void register_jtag_commands(void);

View File

@ -0,0 +1,51 @@
# SPDX-FileCopyrightText: 2022-2024 Espressif Systems (Shanghai) CO LTD
# # SPDX-License-Identifier: CC0-1.0
import logging
import os
import re
import signal
import pexpect
import pytest
from pytest_embedded_idf import IdfDut
def run_gdb_test(dut: IdfDut) -> None:
with open(os.path.join(dut.logdir, 'ocd.txt'), 'w') as ocd_log, \
pexpect.spawn(f'openocd -f board/esp32c6-builtin.cfg',
timeout=60,
logfile=ocd_log,
encoding='utf-8',
codec_errors='ignore') as p:
try:
p.expect(re.compile(r'JTAG tap: esp32c6.cpu tap/device found'), timeout=5)
logging.info('JTAG is enabled.')
except pexpect.TIMEOUT:
logging.info('JTAG is disabled')
finally:
p.terminate()
p.kill(signal.SIGKILL)
@pytest.mark.esp32c6
@pytest.mark.jtag_re_enable
def test_jtag_re_enable(dut: IdfDut) -> None:
dut.expect_exact('esp32c6>', timeout=30)
logging.info('Initially:')
run_gdb_test(dut)
logging.info('After calling enable_jtag:')
# The following token data is generated using the HMAC key:
# {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32}
dut.write('enable_jtag b2a49b1cce1be922bb7e431277413e3e8e6c3e8e6e17625c50ac66a9a857949b')
dut.expect('JTAG re-enablement workflow performed', timeout=30)
run_gdb_test(dut)
logging.info('After calling disable_jtag:')
dut.write('disable_jtag')
dut.expect('JTAG disabled temporarily', timeout=30)
run_gdb_test(dut)

View File

@ -0,0 +1 @@
esptool

View File

@ -0,0 +1,3 @@
# Changing channel for console output
#
CONFIG_ESP_CONSOLE_USB_SERIAL_JTAG=y

View File

@ -103,6 +103,7 @@ ENV_MARKERS = {
'i2c_oled': 'Runner with ssd1306 I2C oled connected',
'httpbin': 'runner for tests that need to access the httpbin service',
'flash_4mb': 'C2 runners with 4 MB flash',
'jtag_re_enable': 'Runner to re-enable jtag which is softly disabled by burning bit SOFT_DIS_JTAG on eFuse',
# multi-dut markers
'multi_dut_modbus_rs485': 'a pair of runners connected by RS485 bus',
'ieee802154': 'ieee802154 related tests should run on ieee802154 runners.',