feat(esp_tee): Added examples demonstrating the ESP-TEE framework

This commit is contained in:
Laukik Hase 2024-11-12 17:03:54 +05:30
parent 909fd60d33
commit ad74c1c3c2
No known key found for this signature in database
GPG Key ID: D6F3208C06086AC8
40 changed files with 1653 additions and 0 deletions

View File

@ -33,3 +33,35 @@ examples/security/nvs_encryption_hmac:
examples/security/security_features_app:
disable:
- if: IDF_TARGET not in ["esp32c3"]
examples/security/tee/tee_attestation:
disable:
- if: IDF_TARGET not in ["esp32c6"]
depends_components:
- esp_tee
depends_filepatterns:
- examples/security/tee/tee_attestation/**/*
examples/security/tee/tee_basic:
disable:
- if: IDF_TARGET not in ["esp32c6"]
depends_components:
- esp_tee
depends_filepatterns:
- examples/security/tee/tee_basic/**/*
examples/security/tee/tee_secure_ota:
disable:
- if: IDF_TARGET not in ["esp32c6"]
depends_components:
- esp_tee
depends_filepatterns:
- examples/security/tee/tee_secure_ota/**/*
examples/security/tee/tee_secure_storage:
disable:
- if: IDF_TARGET not in ["esp32c6"]
depends_components:
- esp_tee
depends_filepatterns:
- examples/security/tee/tee_secure_storage/**/*

View File

@ -37,3 +37,6 @@ The steps include:
* Generating token data from the key.
* Using commands to re-enable JTAG access.
# ESP-TEE (Trusted Execution Environment)
For examples demonstrating the ESP-TEE framework, please refer to the [ESP-TEE examples](tee/README.md) directory. It contains examples showcasing operations like AES encryption/decryption in the TEE, secure storage with signing and encryption capabilities, secure OTA updates for TEE applications, and entity attestation token generation.

View File

@ -0,0 +1,37 @@
# ESP-TEE (Trusted Execution Environment) Examples
(See the README.md file in the upper level 'examples' directory for more information about examples.)
## Overview
A Trusted Execution Environment (TEE) serves as a isolated processing environment where applications can run and data can be stored __securely__. This environment ensures the confidentiality and integrity of sensitive information, safeguarding it from unauthorized access, even in the presence of potential threats in the rest of the system.
## Examples
### `tee_basic`
- In this example, the REE raises a request for AES operation in TEE through the secure service call interface. The TEE performs encrypts/decrypts the given buffer with the `aes-256-cbc` mode using the key protected by TEE.
- The result of the AES operation is returned in the output buffer provided in the secure service call by the REE. It also demonstrates how to create and add a custom service call to the ESP-TEE framework.
### `tee_secure_storage`
- This example demonstrates the ESP-TEE framework's Secure Storage with two workflows:
- Signing and verification
- Create and securely store an ECDSA `secp256r1` keypair in protected memory (secure storage partition)
- Sign a message in TEE using the stored ECDSA keypair from a given slot ID
- Retrieve the corresponding ECDSA public key for verification
- Verify the signature in REE
- Encryption and decryption
- Generate and securely store an AES-256 key in the secure storage partition
- Encrypt a message in the TEE using the AES key stored in the given slot ID with the `aes-256-gcm` algorithm and generate an authentication tag
- Decrypt the ciphertext using the same AES key and validate the authentication tag
- Verify that the decrypted message matches the original
### `tee_secure_ota`
This example illustrates a secure Over-The-Air (OTA) update for the TEE app with rollback support. The REE fetches the new TEE image from the HTTP/S server and securely writes it to the TEE passive partition using the secure service call interface. It also updates the TEE OTA data partition for switching the active TEE partition for subsequent boots.
### `tee_attestation`
- In this example, the REE initiates a request to generate an entity attestation token in the TEE through the secure service call interface. The TEE creates the token, including information like image and ESP-IDF versions, along with SHA256 digests of the bootloader, active TEE, and app images.
- The token is signed using the ECDSA key stored in the designated slot ID of the TEE's Secure Storage. Subsequently, the resulting token is handed back to the REE within the output buffer specified in the secure service call.

View File

@ -0,0 +1,11 @@
# The following 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)
# (Not part of the boilerplate)
# This example uses extra components for the following -
# 1. Printing TEE attestation info
list(APPEND EXTRA_COMPONENT_DIRS $ENV{IDF_PATH}/components/esp_tee/subproject/components/tee_attestation)
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
project(tee_attestation)

View File

@ -0,0 +1,199 @@
| Supported Targets | ESP32-C6 |
| ----------------- | -------- |
# TEE: Attestation example
## Overview
- Attestation is the process of securely providing information about a device to external parties through a cryptographically secured token. It enables IoT devices, especially those without user interfaces or in remote locations, to introduce themselves securely to networks and IoT platforms by offering attestation information.
- In this example, the REE initiates a request to generate an entity attestation token in the TEE through the secure service call interface. The TEE creates the token, including information such as -
- For all firmware images (Bootloader, active TEE and non-secure app)
- Project and ESP-IDF version
- Digest (SHA256)
- Public key corresponding to the private key used for signing (in compressed format)
- Signature (`r` and `s` components)
- The token is signed using the ECDSA key stored in the designated slot ID of the TEE's Secure Storage. Subsequently, the resulting token is handed back to the REE in the output buffer specified in the secure service call.
<details>
<summary><b>Attestation: Sample Token</b></summary>
```json
{
"header": {
"magic": "44fef7cc",
"encr_alg": "",
"sign_alg": "ecdsa_secp256r1_sha256",
"key_id": 0
},
"eat": {
"nonce": -1582119980,
"client_id": 262974944,
"device_ver": 1,
"device_id": "e8cddb2a7f9a5a7c61735d6dda26e4bd153c6d772a9be6f26bd321dfe25e0ac8",
"instance_id": "1adba85e0df997fd961f25a9e312430cef162b5c69466cd5b172f1e65ac7360c",
"psa_cert_ref": "0716053550477-10100",
"device_status": 255,
"sw_claims": {
"tee": {
"type": 1,
"ver": "v0.3.0",
"idf_ver": "v5.1-679-gcba25b2512",
"secure_ver": 0,
"part_chip_rev": {
"min": 0,
"max": 99
},
"part_digest": {
"type": 0,
"calc_digest": "f732e7f285b7de7ac3167a867711eddbf17a2a05513d35e41cd1ebf2e0958b2e",
"digest_validated": true,
"sign_verified": true,
"secure_padding": true
}
},
"app": {
"type": 2,
"ver": "v0.1.0",
"idf_ver": "v5.1-679-gcba25b2512",
"secure_ver": 0,
"part_chip_rev": {
"min": 0,
"max": 99
},
"part_digest": {
"type": 0,
"calc_digest": "21e114fd30b9234c501525990dfab71d00348c531bb64224feff9deb32e66f9f",
"digest_validated": true,
"sign_verified": true,
"secure_padding": true
}
},
"bootloader": {
"type": 0,
"ver": "",
"idf_ver": "",
"secure_ver": -1,
"part_chip_rev": {
"min": 0,
"max": 99
},
"part_digest": {
"type": 0,
"calc_digest": "516148649a7f670b894391ded9d64a0e8604c5cec9a1eeb0014d2549cdaa4725",
"digest_validated": true,
"sign_verified": true
}
}
}
},
"public_key": {
"compressed": "02a45c6c94c4be7722bd2513f4ccbc4daa369747e6e96e0f9f7a2eba055dee6d46"
},
"sign": {
"r": "37bcc8ed9c15a4712c18fe20b257992e5d9ec273b6261675f247667b4575495b",
"s": "28ce15da73880f7d5ee303948769b197077208f1f242aaee448e9ed23f9085fa"
}
}
```
</details>
## How to use the example
### Hardware Required
This example can be executed on any development board with a Espressif SOC chip supporting the TEE framework (see Supported Targets table above).
### Configure the project
Before the project configuration and build, be sure to set the correct chip target using `idf.py set-target <chip_name>`.
Open the project configuration menu (`idf.py menuconfig`).
- Configure the secure storage slot ID for generating/fetching the ECDSA keypair for attestation token signing at `(Top) → Security features → TEE: Secure Storage slot ID for EAT signing`.
### Build and Flash
Build the project and flash it to the board, then run the monitor tool to view the serial output:
```
idf.py -p PORT flash monitor
```
(To exit the serial monitor, type `Ctrl-]`.)
See the Getting Started Guide for full steps to configure and use ESP-IDF to build projects.
### Usage
- Use console commands to dump the attestation info: `tee_att_info`
- The generated token's signature can be verified using the script given below.
<details>
<summary><b>Attestation: Verifying the generated token</b></summary>
**Usage**: `python verify_att_token.py '<eat_json_string>'`
```python
import argparse
import hashlib
import json
from cryptography.hazmat.primitives.asymmetric import utils
from ecdsa.curves import NIST256p
from ecdsa.keys import VerifyingKey
from ecdsa.util import sigdecode_der
# Fetch the token from the console
parser = argparse.ArgumentParser(description='Verify the given Entity Attestation Token')
parser.add_argument('att_token', type=str, help='the EAT string to verify')
args = parser.parse_args()
# Parsing the token
tk_info = json.loads(args.att_token)
# Fetching the data to be verified
tk_hdr_val = json.dumps(tk_info['header'], separators=(',', ':')).encode('latin-1')
tk_eat_val = json.dumps(tk_info['eat'], separators=(',', ':')).encode('latin-1')
tk_pubkey_val = json.dumps(tk_info['public_key'], separators=(',', ':')).encode('latin-1')
# Pre-hashing the data
ctx = hashlib.new('sha256')
ctx.update(tk_hdr_val)
ctx.update(tk_eat_val)
ctx.update(tk_pubkey_val)
digest = ctx.digest()
# Fetching the public key
tk_pubkey_c = bytes.fromhex(tk_info['public_key']['compressed'])
# Fetching the appended signature
tk_sign_r = bytes.fromhex(tk_info['sign']['r'])
tk_sign_s = bytes.fromhex(tk_info['sign']['s'])
# Construct the signature using the R and S components
signature = utils.encode_dss_signature(int.from_bytes(tk_sign_r, 'big'), int.from_bytes(tk_sign_s, 'big'))
# Uncompress the public key and verify the signature
vk = VerifyingKey.from_string(tk_pubkey_c, NIST256p, hashfunc=hashlib.sha256)
assert vk.verify_digest(signature, digest, sigdecode=sigdecode_der)
print('Token signature verified!')
```
</details>
### Example Output
```log
I (416) main_task: Calling app_main()
I (416) example_tee_attest: TEE Attestation Service
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 (476) main_task: Returned from app_main()
esp32c6> tee_att_info
I (6206) cmd_tee_attest: Attestation token - Length: 1525
I (6206) cmd_tee_attest: Attestation token - Data:
'{"header":{"magic":"44fef7cc","encr_alg":"","sign_alg":"ecdsa_secp256r1_sha256","key_id":0},"eat":{"nonce":-1582119980,"client_id":262974944,"device_ver":1,"device_id":"4ecc458ef4290329552b4dcdccb99d55e5ea7624f24c87b27b71515e1666f39c","instance_id":"77eb3dfec7633302fe4bcf04ffe3be5e83c0513057aa070d387f1e8350271329","psa_cert_ref":"0716053550477-10100","device_status":165,"sw_claims":{"tee":{"type":1,"ver":"1.0.0","idf_ver":"v5.5-dev-727-g624f640f61d-dirty","secure_ver":0,"part_chip_rev":{"min":0,"max":99},"part_digest":{"type":0,"calc_digest":"6e6548a5d64cd3d6e2e6dc166384f32f73558fbd9c0c0985c6095d643f053eb5","digest_validated":true,"sign_verified":false,"secure_padding":false}},"app":{"type":2,"ver":"v0.1.0","idf_ver":"v5.5-dev-727-g624f640f61d-dirty","secure_ver":0,"part_chip_rev":{"min":0,"max":99},"part_digest":{"type":0,"calc_digest":"7f10992d4bb32c497184fd2da0e3a593b235d82bde24de868c8eb4636d4b7bdc","digest_validated":true,"sign_verified":false,"secure_padding":false}},"bootloader":{"type":0,"ver":"01000000","idf_ver":"v5.5-dev-727-g624f640f61d-dirty","secure_ver":0,"part_chip_rev":{"min":0,"max":99},"part_digest":{"type":0,"calc_digest":"2cdf1bac1792df04ad10d67287ef3ab7024e183dc32899a190668cbb7d21a5a8","digest_validated":true,"sign_verified":false}}}},"public_key":{"compressed":"030df5c5fd9a4096a58ba16dfc4f1d53781bab555fc307d71367f0afc663005174"},"sign":{"r":"c3a0fc8ce3cd1dec2a0e38c4a63c03bd1e044febd5847178fe304b06d48b3eaf","s":"c8e34bc5d854e728cffdfd701ea09deabc9a9a22c4b06f312a61a1448a56b8b1"}}'
```

View File

@ -0,0 +1,2 @@
idf_component_register(SRCS "app_main.c"
INCLUDE_DIRS ".")

View File

@ -0,0 +1,53 @@
/*
* SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Unlicense OR CC0-1.0
*/
#include <string.h>
#include <inttypes.h>
#include <errno.h>
#include "esp_system.h"
#include "esp_event.h"
#include "esp_log.h"
#include "esp_console.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_tee_attestation.h"
static const char *TAG = "example_tee_attest";
#define ESP_ATT_TK_NONCE (0xA1B2C3D4)
#define ESP_ATT_TK_CLIENT_ID (0x0FACADE0)
#define ESP_ATT_TK_BUF_SIZE (1792)
#define ESP_ATT_TK_PSA_CERT_REF ("0716053550477-10100")
static uint8_t token_buf[ESP_ATT_TK_BUF_SIZE] = {0};
void app_main(void)
{
ESP_LOGI(TAG, "TEE Attestation Service");
uint32_t token_len = 0;
/* Generate entity attestation token using the following parameters
* and return the token length in token_len:
* - Nonce value for freshness
* - Client ID to identify requester
* - PSA certification ID reference string
* - Buffer to store the generated token
*/
esp_err_t err = esp_tee_att_generate_token(ESP_ATT_TK_NONCE, ESP_ATT_TK_CLIENT_ID, (const char *)ESP_ATT_TK_PSA_CERT_REF,
token_buf, sizeof(token_buf), &token_len);
if (err != ESP_OK) {
ESP_LOGE(TAG, "Failed to generate entity attestation token!");
abort();
}
/* Print the generated token details - length and contents */
ESP_LOGI(TAG, "Attestation token - Length: %lu", token_len);
ESP_LOGI(TAG, "Attestation token - Data:\n'%.*s'", (int)token_len, token_buf);
}

View File

@ -0,0 +1,59 @@
# SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
# SPDX-License-Identifier: Unlicense OR CC0-1.0
import hashlib
import json
import logging
from typing import Any
import pytest
from cryptography.hazmat.primitives.asymmetric import utils
from ecdsa.curves import NIST256p
from ecdsa.keys import VerifyingKey
from ecdsa.util import sigdecode_der
from pytest_embedded import Dut
def verify_att_token_signature(att_tk: str) -> Any:
# Parsing the token
tk_info = json.loads(att_tk)
# Fetching the data to be verified
tk_hdr_val = json.dumps(tk_info['header'], separators=(',', ':')).encode('latin-1')
tk_eat_val = json.dumps(tk_info['eat'], separators=(',', ':')).encode('latin-1')
tk_pubkey_val = json.dumps(tk_info['public_key'], separators=(',', ':')).encode('latin-1')
# Pre-hashing the data
ctx = hashlib.new('sha256')
ctx.update(tk_hdr_val)
ctx.update(tk_eat_val)
ctx.update(tk_pubkey_val)
digest = ctx.digest()
# Fetching the public key
tk_pubkey_c = bytes.fromhex(tk_info['public_key']['compressed'])
# Fetching the appended signature
tk_sign_r = bytes.fromhex(tk_info['sign']['r'])
tk_sign_s = bytes.fromhex(tk_info['sign']['s'])
# Construct the signature using the R and S components
signature = utils.encode_dss_signature(int.from_bytes(tk_sign_r, 'big'), int.from_bytes(tk_sign_s, 'big'))
# Uncompress the public key and verify the signature
vk = VerifyingKey.from_string(tk_pubkey_c, NIST256p, hashfunc=hashlib.sha256)
return vk.verify_digest(signature, digest, sigdecode=sigdecode_der)
@pytest.mark.esp32c6
@pytest.mark.generic
def test_example_tee_attestation(dut: Dut) -> None:
# Erase the TEE secure_storage partition
dut.serial.erase_partition('secure_storage')
# Start test
dut.expect('TEE Attestation Service', timeout=30)
dut.expect(r'Attestation token - Length: (\d+)', timeout=30)
att_tk = dut.expect(r"'(.*?)'", timeout=30)[1].decode()
assert verify_att_token_signature(att_tk)
logging.info('Attestation token - Signature verified!')

View File

@ -0,0 +1,4 @@
# Enabling TEE
CONFIG_SECURE_ENABLE_TEE=y
CONFIG_ESPTOOLPY_FLASHSIZE_4MB=y
CONFIG_PARTITION_TABLE_TWO_OTA_TEE=y

View File

@ -0,0 +1 @@
v0.1.0

View File

@ -0,0 +1,9 @@
# The following 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)
include(${CMAKE_CURRENT_LIST_DIR}/components/example_secure_service/tee_project.cmake)
project(tee_basic)

View File

@ -0,0 +1,57 @@
| Supported Targets | ESP32-C6 |
| ----------------- | -------- |
# Basic TEE example
## Overview
- This example illustrates the ESP-TEE (Trusted Execution Environment) framework to encrypt/decrypt data using AES within a secure environment.
- The non-secure world i.e. the Rich Execution Environment (REE) raises a request for AES operation in TEE through the secure service call interface. The TEE performs encrypts/decrypts the given buffer with the AES-256-CBC mode using the key protected by TEE. If the operation is successful, the result of the AES operation is returned in the output buffer provided in the secure service call by the REE.
- This example also demonstrates how to add custom service calls to TEE. You can refer to `components/example_service` for more information - see the structure below.
```
└── example_secure_service # Component parent directory
├── CMakeLists.txt
├── example_service.c # Custom secure service APIs
├── example.tbl # Custom secure service table, which is appended to the default one provided by TEE
├── include
│   └── example_service.h
└── tee_project.cmake # To be manually included in the project's top level CMakeLists.txt before project(...)
# Processes the custom service table
```
## How to use the example
### Hardware Required
This example can be executed on any development board with a Espressif SOC chip supporting the TEE framework (see Supported Targets table above).
### Build and Flash
Before building the example, be sure to set the correct chip target using idf.py set-target <chip_name>.
Build the project and flash it to the board, then run the monitor tool to view the serial output:
```
idf.py -p PORT flash monitor
```
(To exit the serial monitor, type `Ctrl-]`.)
See the Getting Started Guide for full steps to configure and use ESP-IDF to build projects.
### Example Output
```log
I (315) main_task: Calling app_main()
I (315) example_tee_basic: AES-256-CBC operations in TEE
TEE: Secure service call for AES-256-CBC operation
TEE: In PROTECTED M-mode
I (325) example_tee_basic: AES encryption successful!
I (325) example_tee_basic: Cipher text -
I (325) example_tee_basic: ee 04 9b ee 95 6f 25 04 1e 8c e4 4e 8e 4e 7a d3
TEE: Secure service call for AES-256-CBC operation
TEE: In PROTECTED M-mode
I (345) example_tee_basic: AES decryption successful!
I (345) main_task: Returned from app_main()
```

View File

@ -0,0 +1,11 @@
cmake_minimum_required(VERSION 3.16)
idf_build_get_property(esp_tee_build ESP_TEE_BUILD)
if(NOT esp_tee_build)
return()
endif()
idf_component_register(SRCS "example_service.c"
INCLUDE_DIRS include
PRIV_REQUIRES main)

View File

@ -0,0 +1,2 @@
# SS no. API type Function Args
201 custom example_sec_serv_aes_op 6

View File

@ -0,0 +1,39 @@
/*
* SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Unlicense OR CC0-1.0
*/
#include "esp_cpu.h"
#include "esp_err.h"
#include "esp_rom_sys.h"
#include "hal/aes_hal.h"
#include "aes/esp_aes.h"
#include "esp_tee.h"
#include "secure_service_num.h"
/* Fixed key */
static const uint8_t key[AES_256_KEY_BYTES] = {[0 ... 31] = 0xA5};
esp_err_t _ss_example_sec_serv_aes_op(int mode, size_t length, unsigned char iv[16], const unsigned char *input, unsigned char *output)
{
if (length == 0 || iv == NULL || input == NULL || output == NULL) {
return ESP_ERR_INVALID_ARG;
}
if (esp_cpu_get_curr_privilege_level() != ESP_CPU_S_MODE) {
esp_rom_printf("Operation executing from illegal privilege level!\n");
return ESP_ERR_INVALID_STATE;
}
esp_rom_printf("TEE: Secure service call for AES-256-CBC operation\n");
esp_rom_printf("TEE: In PROTECTED M-mode\n");
esp_aes_context ctx = {};
ctx.key_bytes = AES_256_KEY_BYTES;
ctx.key_in_hardware = 0;
memcpy(ctx.key, key, ctx.key_bytes);
return (esp_err_t)esp_aes_crypt_cbc(&ctx, mode, length, iv, input, output);
}

View File

@ -0,0 +1,20 @@
/*
* SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Unlicense OR CC0-1.0
*/
#include "esp_err.h"
/**
* @brief Perform AES-256-CBC encryption/decryption operation in TEE
*
* @param mode ESP_AES_ENCRYPT (1) for encryption, ESP_AES_DECRYPT (0) for decryption
* @param length Length of input data in bytes
* @param iv Initialization vector (16 bytes)
* @param input Input buffer containing plaintext (for encryption) or ciphertext (for decryption)
* @param output Output buffer for ciphertext (for encryption) or plaintext (for decryption)
*
* @return esp_err_t ESP_OK on success, appropriate error code on failure
*/
esp_err_t example_sec_serv_aes_op(int mode, size_t length, unsigned char iv[16], const unsigned char *input, unsigned char *output);

View File

@ -0,0 +1,15 @@
# tee_project.cmake file must be manually included in the project's top level CMakeLists.txt before project()
# This ensures that the variables are set before TEE starts building
get_filename_component(directory "${CMAKE_CURRENT_LIST_DIR}/.." ABSOLUTE DIRECTORY)
get_filename_component(name ${CMAKE_CURRENT_LIST_DIR} NAME)
# Append secure service table consisting of secure services
idf_build_set_property(CUSTOM_SECURE_SERVICE_TBL ${CMAKE_CURRENT_LIST_DIR}/example.tbl APPEND)
# Append the directory of this component which is used by esp_tee component as
# EXTRA_COMPONENT_DIRS
idf_build_set_property(CUSTOM_SECURE_SERVICE_COMPONENT_DIR ${directory} APPEND)
# Append the name of the component so that esp_tee can include it in its COMPONENTS list
idf_build_set_property(CUSTOM_SECURE_SERVICE_COMPONENT ${name} APPEND)

View File

@ -0,0 +1,2 @@
idf_component_register(SRCS "tee_main.c"
INCLUDE_DIRS "")

View File

@ -0,0 +1,64 @@
/* ESP-TEE (Trusted Execution Environment) Example
*
* SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Unlicense OR CC0-1.0
*/
#include <stdio.h>
#include <string.h>
#include <inttypes.h>
#include "esp_log.h"
#include "esp_err.h"
#include "aes/esp_aes.h"
#include "esp_tee.h"
#include "secure_service_num.h"
#define BUF_SZ (16)
static const char *TAG = "example_tee_basic";
static const uint8_t expected_cipher[] = {
0xee, 0x04, 0x9b, 0xee, 0x95, 0x6f, 0x25, 0x04,
0x1e, 0x8c, 0xe4, 0x4e, 0x8e, 0x4e, 0x7a, 0xd3
};
static const uint8_t nonce[IV_BYTES] = {[0 ... IV_BYTES - 1] = 0xFF};
/*
* Example workflow:
* 1. The REE initiates an AES operation request via the secure service call interface
* 2. The TEE receives the request and performs encryption/decryption using AES-256-CBC mode
* 3. The TEE uses a protected key that is only accessible within the secure environment
* 4. The encrypted/decrypted result is returned to the non-secure world through an output buffer
* provided in the secure service call
*/
void app_main(void)
{
ESP_LOGI(TAG, "AES-256-CBC operations in TEE");
uint8_t plain_text[BUF_SZ] = {[0 ... BUF_SZ - 1] = 0x3A};
uint8_t cipher_text[BUF_SZ] = {0};
uint8_t decrypted_text[BUF_SZ] = {0};
uint8_t iv[IV_BYTES] = {0};
memcpy(iv, nonce, sizeof(iv));
uint32_t ret = esp_tee_service_call(6, SS_EXAMPLE_SEC_SERV_AES_OP, ESP_AES_ENCRYPT, sizeof(plain_text), iv, plain_text, cipher_text);
if (ret != ESP_OK || memcmp(cipher_text, expected_cipher, sizeof(expected_cipher))) {
ESP_LOGE(TAG, "Failed to encrypt data!");
} else {
ESP_LOGI(TAG, "AES encryption successful!");
}
ESP_LOGI(TAG, "Cipher text -");
ESP_LOG_BUFFER_HEX_LEVEL(TAG, cipher_text, sizeof(cipher_text), ESP_LOG_INFO);
memcpy(iv, nonce, sizeof(iv));
ret = esp_tee_service_call(6, SS_EXAMPLE_SEC_SERV_AES_OP, ESP_AES_DECRYPT, sizeof(cipher_text), iv, cipher_text, decrypted_text);
if (ret != ESP_OK || memcmp(decrypted_text, plain_text, sizeof(plain_text))) {
ESP_LOGE(TAG, "Failed to decrypt data!");
} else {
ESP_LOGI(TAG, "AES decryption successful!");
}
}

View File

@ -0,0 +1,28 @@
# SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
# SPDX-License-Identifier: Unlicense OR CC0-1.0
import logging
import os
import pytest
from pytest_embedded import Dut
@pytest.mark.esp32c6
@pytest.mark.generic
def test_example_tee_basic(dut: Dut) -> None:
# Logging example binary details
binary_files = [
('tee_basic.bin', '[REE] tee_basic_bin_size'),
('esp_tee/esp_tee.bin', '[TEE] tee_basic_bin_size'),
]
for file_name, log_label in binary_files:
binary_file = os.path.join(dut.app.binary_path, file_name)
bin_size = os.path.getsize(binary_file)
logging.info('{}: {}KB'.format(log_label, bin_size // 1024))
# Start test
dut.expect('AES-256-CBC operations in TEE', timeout=30)
dut.expect('TEE: In PROTECTED M-mode', timeout=30)
dut.expect('AES encryption successful!', timeout=30)
dut.expect('ee 04 9b ee 95 6f 25 04 1e 8c e4 4e 8e 4e 7a d3', timeout=30)
dut.expect('AES decryption successful!', timeout=30)

View File

@ -0,0 +1,3 @@
# Enabling TEE
CONFIG_SECURE_ENABLE_TEE=y
CONFIG_PARTITION_TABLE_SINGLE_APP_TEE=y

View File

@ -0,0 +1,13 @@
# The following 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)
# (Not part of the boilerplate)
# This example uses extra components for the following -
# 1. common functions such as Wi-Fi and Ethernet connection.
# 2. managing TEE OTA updates
list(APPEND EXTRA_COMPONENT_DIRS $ENV{IDF_PATH}/examples/common_components/protocol_examples_common
$ENV{IDF_PATH}/components/esp_tee/subproject/components/tee_ota_ops)
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
project(tee_secure_ota)

View File

@ -0,0 +1,46 @@
| Supported Targets | ESP32-C6 |
| ----------------- | -------- |
# TEE: Secure OTA example
## Overview
- This example illustrates a secure Over-The-Air (OTA) update for the TEE app as well as the non-secure app.
- For the TEE update, the REE fetches the new TEE image from the HTTP/S server and securely writes it to the TEE passive partition using the secure service call interface. It also updates the TEE OTA data partition for switching the active TEE partition for subsequent boots.
- During the next boot, if the TEE image self-test fails, the device will revert to the old TEE image. Otherwise, the passive TEE partition will be erased, and the device will proceed to boot into the REE normally.
- The user OTA workflow is based on the `esp_https_ota` component APIs.
## How to use the example
### Hardware Required
This example can be executed on any development board with a Espressif SOC chip supporting the TEE framework (see Supported Targets table above).
### Configure the project
Before the project configuration and build, be sure to set the correct chip target using `idf.py set-target <chip_name>`.
Open the project configuration menu (`idf.py menuconfig`).
- Set the Wi-Fi SSID and password at `(Top) → Example Connection Configuration → WiFi SSID / WiFi Password`
### Build and Flash
Build the project and flash it to the board, then run the monitor tool to view the serial output:
```
idf.py -p PORT flash monitor
```
(To exit the serial monitor, type `Ctrl-]`.)
See the Getting Started Guide for full steps to configure and use ESP-IDF to build projects.
### Usage
- Use console commands to perform OTA for the TEE app as well as the non-secure app: `tee_ota <img_url>` or `user_ota <img_url>`
#### Notes
- This example uses the ESP-IDF certificate bundle by default, so you can host your image online and download it directly.
- For testing purposes, if you want to start a local HTTP/S server, you can refer to the guide in OTA examples parent directory - see [here](../../../system/ota/). Note that you will need to embed the CA certificate in the firmware for such cases.

View File

@ -0,0 +1,2 @@
idf_component_register(SRCS "cmd_ota.c" "app_main.c"
INCLUDE_DIRS ".")

View File

@ -0,0 +1,17 @@
menu "Example Configuration"
orsource "$IDF_PATH/examples/common_components/env_caps/$IDF_TARGET/Kconfig.env_caps"
config EXAMPLE_SKIP_COMMON_NAME_CHECK
bool "Skip server certificate CN fieldcheck"
default n
help
This allows you to skip the validation of OTA server certificate CN field.
config EXAMPLE_OTA_RECV_TIMEOUT
int "OTA Receive Timeout"
default 5000
help
Maximum time for reception
endmenu

View File

@ -0,0 +1,123 @@
/*
* SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Unlicense OR CC0-1.0
*/
#include <string.h>
#include <inttypes.h>
#include <errno.h>
#include "esp_system.h"
#include "esp_event.h"
#include "esp_log.h"
#include "esp_console.h"
#include "nvs.h"
#include "nvs_flash.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#if CONFIG_EXAMPLE_CONNECT_WIFI
#include "esp_wifi.h"
#endif
#include "protocol_examples_common.h"
/* TODO: Workaround for pytest failure */
#if CONFIG_EXAMPLE_WIFI_SSID_PWD_FROM_STDIN
#include "driver/uart.h"
#endif
#include "cmd_ota.h"
#define PROMPT_STR CONFIG_IDF_TARGET
static const char *TAG = "example_tee_secure_ota";
void setup_console(void)
{
esp_console_repl_t *repl = NULL;
esp_console_repl_config_t repl_config = ESP_CONSOLE_REPL_CONFIG_DEFAULT();
/* Prompt to be printed before each line.
* This can be customized, made dynamic, etc.
*/
repl_config.prompt = PROMPT_STR ">";
repl_config.max_cmdline_length = 128;
/* Register help command */
ESP_ERROR_CHECK(esp_console_register_help_command());
/* Register custom commands */
const esp_console_cmd_t tee_ota_cmd = {
.command = "tee_ota",
.help = "Initiate TEE app OTA - signature: tee_ota <tee_ota_url>",
.hint = NULL,
.func = &cmd_tee_app_ota_task,
};
ESP_ERROR_CHECK(esp_console_cmd_register(&tee_ota_cmd));
const esp_console_cmd_t user_ota_cmd = {
.command = "user_ota",
.help = "Initiate User app OTA - signature: user_ota <user_ota_url>",
.hint = NULL,
.func = &cmd_user_app_ota_task,
};
ESP_ERROR_CHECK(esp_console_cmd_register(&user_ota_cmd));
/* TODO: For test cases, the SSID and password is taken through stdin,
* which installs the UART driver but does not delete it. This causes an
* issue when we try to install the UART driver again when initialising the console.
*/
#if CONFIG_EXAMPLE_WIFI_SSID_PWD_FROM_STDIN
ESP_ERROR_CHECK(uart_driver_delete(CONFIG_ESP_CONSOLE_UART_NUM));
#endif
#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));
}
void app_main(void)
{
ESP_LOGI(TAG, "OTA with TEE enabled");
// Initialize NVS.
esp_err_t err = nvs_flash_init();
if (err == ESP_ERR_NVS_NO_FREE_PAGES || err == ESP_ERR_NVS_NEW_VERSION_FOUND) {
// OTA app partition table has a smaller NVS partition size than the non-OTA
// partition table. This size mismatch may cause NVS initialization to fail.
// If this happens, we erase NVS partition and initialize NVS again.
ESP_ERROR_CHECK(nvs_flash_erase());
err = nvs_flash_init();
}
ESP_ERROR_CHECK( err );
ESP_ERROR_CHECK(esp_netif_init());
ESP_ERROR_CHECK(esp_event_loop_create_default());
/* This helper function configures Wi-Fi or Ethernet, as selected in menuconfig.
* Read "Establishing Wi-Fi or Ethernet Connection" section in
* examples/protocols/README.md for more information about this function.
*/
ESP_ERROR_CHECK(example_connect());
#if CONFIG_EXAMPLE_CONNECT_WIFI
/* Ensure to disable any WiFi power save mode, this allows best throughput
* and hence timings for overall OTA operation.
*/
esp_wifi_set_ps(WIFI_PS_NONE);
#endif // CONFIG_EXAMPLE_CONNECT_WIFI
setup_console();
}

View File

@ -0,0 +1,251 @@
/*
* SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Unlicense OR CC0-1.0
*/
#include <string.h>
#include <inttypes.h>
#include <errno.h>
#include "esp_system.h"
#include "esp_event.h"
#include "esp_log.h"
#include "esp_ota_ops.h"
#include "esp_app_format.h"
#include "esp_image_format.h"
#include "esp_flash_partitions.h"
#include "esp_partition.h"
#include "esp_crt_bundle.h"
#include "esp_http_client.h"
#include "esp_https_ota.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_tee_ota_ops.h"
#define BUF_SIZE 512
static const char *TAG = "cmd_ota";
/* Semaphore governing the TEE and User app OTA processes; only one should be active at a time */
static SemaphoreHandle_t s_ota_mgmt;
static void http_cleanup(esp_http_client_handle_t client)
{
esp_http_client_close(client);
esp_http_client_cleanup(client);
}
static void task_fatal_error(void)
{
ESP_LOGE(TAG, "Exiting task due to fatal error...");
(void)vTaskDelete(NULL);
}
static esp_err_t setup_task_conn(esp_http_client_config_t *config, const char *url)
{
if (config == NULL || url == NULL) {
ESP_LOGE(TAG, "Invalid arguments!");
return ESP_ERR_INVALID_ARG;
}
/* Wait for the semaphore to be free for the taking */
if (xSemaphoreTake(s_ota_mgmt, pdMS_TO_TICKS(1000)) != pdTRUE) {
ESP_LOGE(TAG, "Other OTA already in progress!");
return ESP_FAIL;
}
config->url = url;
config->timeout_ms = CONFIG_EXAMPLE_OTA_RECV_TIMEOUT;
config->keep_alive_enable = true;
config->crt_bundle_attach = esp_crt_bundle_attach;
#ifdef CONFIG_EXAMPLE_SKIP_COMMON_NAME_CHECK
config->skip_cert_common_name_check = true;
#endif
return ESP_OK;
}
static void tee_ota_task(void *pvParameter)
{
ESP_LOGI(TAG, "Starting TEE OTA...");
esp_http_client_config_t config = {};
if (setup_task_conn(&config, (const char *)pvParameter) != ESP_OK) {
ESP_LOGE(TAG, "Failed to setup OTA task");
task_fatal_error();
}
esp_http_client_handle_t client = esp_http_client_init(&config);
if (client == NULL) {
ESP_LOGE(TAG, "Failed to initialise HTTP connection");
xSemaphoreGive(s_ota_mgmt);
task_fatal_error();
}
esp_err_t err = esp_http_client_open(client, 0);
if (err != ESP_OK) {
ESP_LOGE(TAG, "Failed to open HTTP connection: %s", esp_err_to_name(err));
esp_http_client_cleanup(client);
xSemaphoreGive(s_ota_mgmt);
task_fatal_error();
}
esp_http_client_fetch_headers(client);
uint32_t curr_write_offset = 0;
bool image_header_was_checked = false; /* deal with all receive packet */
char ota_write_data[BUF_SIZE + 1] = {0}; /* an ota data write buffer ready to write to the flash */
while (1) {
int data_read = esp_http_client_read(client, ota_write_data, BUF_SIZE);
if (data_read < 0) {
ESP_LOGE(TAG, "Error: SSL data read error");
http_cleanup(client);
xSemaphoreGive(s_ota_mgmt);
task_fatal_error();
} else if (data_read > 0) {
if (image_header_was_checked == false) {
/* TODO: TEE image header is missing the `esp_app_desc_t` configuration structure */
if (data_read > sizeof(esp_image_header_t) + sizeof(esp_image_segment_header_t)) {
esp_image_header_t img_hdr;
memcpy(&img_hdr, ota_write_data, sizeof(esp_image_header_t));
if (img_hdr.chip_id != CONFIG_IDF_FIRMWARE_CHIP_ID) {
ESP_LOGE(TAG, "Mismatch chip id, expected %d, found %d", CONFIG_IDF_FIRMWARE_CHIP_ID, img_hdr.chip_id);
http_cleanup(client);
xSemaphoreGive(s_ota_mgmt);
task_fatal_error();
}
image_header_was_checked = true;
err = esp_tee_ota_begin();
if (err != ESP_OK) {
ESP_LOGE(TAG, "esp_tee_ota_begin failed (%s)", esp_err_to_name(err));
http_cleanup(client);
xSemaphoreGive(s_ota_mgmt);
task_fatal_error();
}
ESP_LOGI(TAG, "esp_tee_ota_begin succeeded");
} else {
ESP_LOGE(TAG, "received package is not fit len");
http_cleanup(client);
xSemaphoreGive(s_ota_mgmt);
task_fatal_error();
}
}
err = esp_tee_ota_write(curr_write_offset, (const void *)ota_write_data, data_read);
if (err != ESP_OK) {
http_cleanup(client);
xSemaphoreGive(s_ota_mgmt);
task_fatal_error();
}
curr_write_offset += data_read;
memset(ota_write_data, 0x00, sizeof(ota_write_data));
ESP_LOGD(TAG, "Written image length: %lu", curr_write_offset);
} else if (data_read == 0) {
/*
* As esp_http_client_read never returns negative error code, we rely on
* `errno` to check for underlying transport connectivity closure if any
*/
if (errno == ECONNRESET || errno == ENOTCONN) {
ESP_LOGE(TAG, "Connection closed, errno = %d", errno);
break;
}
if (esp_http_client_is_complete_data_received(client) == true) {
ESP_LOGI(TAG, "Connection closed");
break;
}
}
}
ESP_LOGI(TAG, "esp_tee_ota_write succeeded");
ESP_LOGI(TAG, "Total binary data written: %lu", curr_write_offset);
if (esp_http_client_is_complete_data_received(client) != true) {
ESP_LOGE(TAG, "Error in receiving complete file");
http_cleanup(client);
xSemaphoreGive(s_ota_mgmt);
task_fatal_error();
}
err = esp_tee_ota_end();
if (err != ESP_OK) {
ESP_LOGE(TAG, "esp_ota_end failed (%s)!", esp_err_to_name(err));
http_cleanup(client);
xSemaphoreGive(s_ota_mgmt);
task_fatal_error();
}
ESP_LOGI(TAG, "esp_tee_ota_end succeeded");
/* Ending connection, freeing the semaphore */
http_cleanup(client);
xSemaphoreGive(s_ota_mgmt);
ESP_LOGI(TAG, "Prepare to restart system!");
esp_restart();
return;
}
static void user_ota_task(void *pvParameter)
{
ESP_LOGI(TAG, "Starting User OTA task...");
esp_http_client_config_t config = {};
if (setup_task_conn(&config, (const char *)pvParameter) != 0) {
ESP_LOGE(TAG, "Failed to setup OTA task");
task_fatal_error();
}
esp_https_ota_config_t ota_config = {
.http_config = &config,
};
ESP_LOGI(TAG, "Attempting to download update from %s", config.url);
esp_err_t ret = esp_https_ota(&ota_config);
if (ret == ESP_OK) {
ESP_LOGI(TAG, "OTA Succeed, Rebooting...");
esp_restart();
} else {
ESP_LOGE(TAG, "Firmware upgrade failed");
}
xSemaphoreGive(s_ota_mgmt);
task_fatal_error();
}
static void init_ota_sem(void)
{
static bool first_call = true;
if (first_call) {
s_ota_mgmt = xSemaphoreCreateBinary();
xSemaphoreGive(s_ota_mgmt);
first_call = false;
}
}
static int create_ota_task(int argc, char **argv, const char *task_name, void (*ota_task)(void *))
{
if (argc != 2) {
ESP_LOGE(TAG, "Incorrect number of arguments given!");
return ESP_ERR_INVALID_ARG;
}
init_ota_sem();
if (xTaskCreate(ota_task, task_name, configMINIMAL_STACK_SIZE * 3, argv[1], 5, NULL) != pdPASS) {
ESP_LOGE(TAG, "Task creation failed for %s", task_name);
return ESP_FAIL;
}
return ESP_OK;
}
int cmd_tee_app_ota_task(int argc, char **argv)
{
return create_ota_task(argc, argv, "tee_ota_task", &tee_ota_task);
}
int cmd_user_app_ota_task(int argc, char **argv)
{
return create_ota_task(argc, argv, "user_ota_task", &user_ota_task);
}

View File

@ -0,0 +1,19 @@
/*
* SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Unlicense OR CC0-1.0
*/
#pragma once
#ifdef __cplusplus
extern "C" {
#endif
int cmd_tee_app_ota_task(int argc, char **argv);
int cmd_user_app_ota_task(int argc, char **argv);
#ifdef __cplusplus
}
#endif

View File

@ -0,0 +1,115 @@
# SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
# SPDX-License-Identifier: Unlicense OR CC0-1.0
import http.server
import multiprocessing
import os
import socket
import ssl
import time
from typing import Callable
import pexpect
import pytest
from common_test_methods import get_env_config_variable
from common_test_methods import get_host_ip4_by_dest_ip
from pytest_embedded import Dut
from RangeHTTPServer import RangeRequestHandler
server_file = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'test_certs/server_cert.pem')
key_file = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'test_certs/server_key.pem')
def https_request_handler() -> Callable[...,http.server.BaseHTTPRequestHandler]:
"""
Returns a request handler class that handles broken pipe exception
"""
class RequestHandler(RangeRequestHandler):
def finish(self) -> None:
try:
if not self.wfile.closed:
self.wfile.flush()
self.wfile.close()
except socket.error:
pass
self.rfile.close()
def handle(self) -> None:
try:
RangeRequestHandler.handle(self)
except socket.error:
pass
return RequestHandler
def start_https_server(ota_image_dir: str, server_ip: str, server_port: int) -> None:
os.chdir(ota_image_dir)
requestHandler = https_request_handler()
httpd = http.server.HTTPServer((server_ip, server_port), requestHandler)
httpd.socket = ssl.wrap_socket(httpd.socket,
keyfile=key_file,
certfile=server_file, server_side=True)
httpd.serve_forever()
@pytest.mark.esp32c6
@pytest.mark.wifi_high_traffic
def test_examples_tee_secure_ota_example(dut: Dut) -> None:
"""
This is a positive test case, which downloads complete binary file multiple number of times.
Number of iterations can be specified in variable iterations.
steps:
1. join AP
2. Fetch TEE OTA image over HTTPS
3. Reboot with the new TEE OTA image
"""
# Number of iterations to validate OTA
iterations = 4
server_port = 8001
tee_bin = 'esp_tee/esp_tee.bin'
user_bin = 'tee_secure_ota.bin'
# Start server
thread1 = multiprocessing.Process(target=start_https_server, args=(dut.app.binary_path, '0.0.0.0', server_port))
thread1.daemon = True
thread1.start()
time.sleep(1)
try:
# start test
for i in range(iterations):
# Boot up sequence checks
dut.expect('Loaded TEE app from partition at offset', timeout=30)
dut.expect('Loaded app from partition at offset', timeout=30)
# Starting the test
dut.expect('OTA with TEE enabled', timeout=30)
time.sleep(1)
# Connecting to Wi-Fi
if dut.app.sdkconfig.get('EXAMPLE_WIFI_SSID_PWD_FROM_STDIN') is True:
dut.expect('Please input ssid password:')
env_name = 'wifi_high_traffic'
ap_ssid = get_env_config_variable(env_name, 'ap_ssid')
ap_password = get_env_config_variable(env_name, 'ap_password')
dut.write(f'{ap_ssid} {ap_password}')
try:
ip_address = dut.expect(r'IPv4 address: (\d+\.\d+\.\d+\.\d+)[^\d]', timeout=30)[1].decode()
print('Connected to AP/Ethernet with IP: {}'.format(ip_address))
except pexpect.exceptions.TIMEOUT:
raise ValueError('ENV_TEST_FAILURE: Cannot connect to AP')
host_ip = get_host_ip4_by_dest_ip(ip_address)
dut.expect('Returned from app_main', timeout=30)
# User OTA for last iteration
if i == (iterations - 1):
dut.write(f'user_ota https://{host_ip}:{str(server_port)}/{user_bin}')
dut.expect('OTA Succeed, Rebooting', timeout=150)
else:
dut.write(f'tee_ota https://{host_ip}:{str(server_port)}/{tee_bin}')
dut.expect('esp_tee_ota_end succeeded', timeout=60)
dut.expect('Prepare to restart system!', timeout=60)
finally:
thread1.terminate()

View File

@ -0,0 +1,11 @@
# Connection configuration
CONFIG_EXAMPLE_WIFI_SSID_PWD_FROM_STDIN=y
# Test-specific
CONFIG_EXAMPLE_SKIP_COMMON_NAME_CHECK=y
CONFIG_EXAMPLE_OTA_RECV_TIMEOUT=10000
# Custom certificates for testing
CONFIG_MBEDTLS_CERTIFICATE_BUNDLE_DEFAULT_CMN=y
CONFIG_MBEDTLS_CUSTOM_CERTIFICATE_BUNDLE=y
CONFIG_MBEDTLS_CUSTOM_CERTIFICATE_BUNDLE_PATH="test_certs/server_cert.pem"

View File

@ -0,0 +1,4 @@
# Enabling TEE
CONFIG_SECURE_ENABLE_TEE=y
CONFIG_ESPTOOLPY_FLASHSIZE_4MB=y
CONFIG_PARTITION_TABLE_TWO_OTA_TEE=y

View File

@ -0,0 +1,20 @@
-----BEGIN CERTIFICATE-----
MIIDWDCCAkACCQCbF4+gVh/MLjANBgkqhkiG9w0BAQsFADBuMQswCQYDVQQGEwJJ
TjELMAkGA1UECAwCTUgxDDAKBgNVBAcMA1BVTjEMMAoGA1UECgwDRVNQMQwwCgYD
VQQLDANFU1AxDDAKBgNVBAMMA0VTUDEaMBgGCSqGSIb3DQEJARYLZXNwQGVzcC5j
b20wHhcNMjEwNzEyMTIzNjI3WhcNNDEwNzA3MTIzNjI3WjBuMQswCQYDVQQGEwJJ
TjELMAkGA1UECAwCTUgxDDAKBgNVBAcMA1BVTjEMMAoGA1UECgwDRVNQMQwwCgYD
VQQLDANFU1AxDDAKBgNVBAMMA0VTUDEaMBgGCSqGSIb3DQEJARYLZXNwQGVzcC5j
b20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDhxF/y7bygndxPwiWL
SwS9LY3uBMaJgup0ufNKVhx+FhGQOu44SghuJAaH3KkPUnt6SOM8jC97/yQuc32W
ukI7eBZoA12kargSnzdv5m5rZZpd+NznSSpoDArOAONKVlzr25A1+aZbix2mKRbQ
S5w9o1N2BriQuSzd8gL0Y0zEk3VkOWXEL+0yFUT144HnErnD+xnJtHe11yPO2fEz
YaGiilh0ddL26PXTugXMZN/8fRVHP50P2OG0SvFpC7vghlLp4VFM1/r3UJnvL6Oz
3ALc6dhxZEKQucqlpj8l1UegszQToopemtIj0qXTHw2+uUnkUyWIPjPC+wdOAoap
rFTRAgMBAAEwDQYJKoZIhvcNAQELBQADggEBAItw24y565k3C/zENZlxyzto44ud
IYPQXN8Fa2pBlLe1zlSIyuaA/rWQ+i1daS8nPotkCbWZyf5N8DYaTE4B0OfvoUPk
B5uGDmbuk6akvlB5BGiYLfQjWHRsK9/4xjtIqN1H58yf3QNROuKsPAeywWS3Fn32
3//OpbWaClQePx6udRYMqAitKR+QxL7/BKZQsX+UyShuq8hjphvXvk0BW8ONzuw9
RcoORxM0FzySYjeQvm4LhzC/P3ZBhEq0xs55aL2a76SJhq5hJy7T/Xz6NFByvlrN
lFJJey33KFrAf5vnV9qcyWFIo7PYy2VsaaEjFeefr7q3sTFSMlJeadexW2Y=
-----END CERTIFICATE-----

View File

@ -0,0 +1,28 @@
-----BEGIN PRIVATE KEY-----
MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDhxF/y7bygndxP
wiWLSwS9LY3uBMaJgup0ufNKVhx+FhGQOu44SghuJAaH3KkPUnt6SOM8jC97/yQu
c32WukI7eBZoA12kargSnzdv5m5rZZpd+NznSSpoDArOAONKVlzr25A1+aZbix2m
KRbQS5w9o1N2BriQuSzd8gL0Y0zEk3VkOWXEL+0yFUT144HnErnD+xnJtHe11yPO
2fEzYaGiilh0ddL26PXTugXMZN/8fRVHP50P2OG0SvFpC7vghlLp4VFM1/r3UJnv
L6Oz3ALc6dhxZEKQucqlpj8l1UegszQToopemtIj0qXTHw2+uUnkUyWIPjPC+wdO
AoaprFTRAgMBAAECggEAE0HCxV/N1Q1h+1OeDDGL5+74yjKSFKyb/vTVcaPCrmaH
fPvp0ddOvMZJ4FDMAsiQS6/n4gQ7EKKEnYmwTqj4eUYW8yxGUn3f0YbPHbZT+Mkj
z5woi3nMKi/MxCGDQZX4Ow3xUQlITUqibsfWcFHis8c4mTqdh4qj7xJzehD2PVYF
gNHZsvVj6MltjBDAVwV1IlGoHjuElm6vuzkfX7phxcA1B4ZqdYY17yCXUnvui46z
Xn2kUTOOUCEgfgvGa9E+l4OtdXi5IxjaSraU+dlg2KsE4TpCuN2MEVkeR5Ms3Y7Q
jgJl8vlNFJDQpbFukLcYwG7rO5N5dQ6WWfVia/5XgQKBgQD74at/bXAPrh9NxPmz
i1oqCHMDoM9sz8xIMZLF9YVu3Jf8ux4xVpRSnNy5RU1gl7ZXbpdgeIQ4v04zy5aw
8T4tu9K3XnR3UXOy25AK0q+cnnxZg3kFQm+PhtOCKEFjPHrgo2MUfnj+EDddod7N
JQr9q5rEFbqHupFPpWlqCa3QmQKBgQDldWUGokNaEpmgHDMnHxiibXV5LQhzf8Rq
gJIQXb7R9EsTSXEvsDyqTBb7PHp2Ko7rZ5YQfyf8OogGGjGElnPoU/a+Jij1gVFv
kZ064uXAAISBkwHdcuobqc5EbG3ceyH46F+FBFhqM8KcbxJxx08objmh58+83InN
P9Qr25Xw+QKBgEGXMHuMWgQbSZeM1aFFhoMvlBO7yogBTKb4Ecpu9wI5e3Kan3Al
pZYltuyf+VhP6XG3IMBEYdoNJyYhu+nzyEdMg8CwXg+8LC7FMis/Ve+o7aS5scgG
1to/N9DK/swCsdTRdzmc/ZDbVC+TuVsebFBGYZTyO5KgqLpezqaIQrTxAoGALFCU
10glO9MVyl9H3clap5v+MQ3qcOv/EhaMnw6L2N6WVT481tnxjW4ujgzrFcE4YuxZ
hgwYu9TOCmeqopGwBvGYWLbj+C4mfSahOAs0FfXDoYazuIIGBpuv03UhbpB1Si4O
rJDfRnuCnVWyOTkl54gKJ2OusinhjztBjcrV1XkCgYEA3qNi4uBsPdyz9BZGb/3G
rOMSw0CaT4pEMTLZqURmDP/0hxvTk1polP7O/FYwxVuJnBb6mzDa0xpLFPTpIAnJ
YXB8xpXU69QVh+EBbemdJWOd+zp5UCfXvb2shAeG3Tn/Dz4cBBMEUutbzP+or0nG
vSXnRLaxQhooWm+IuX9SuBQ=
-----END PRIVATE KEY-----

View File

@ -0,0 +1,9 @@
# The following 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)
list(APPEND EXTRA_COMPONENT_DIRS $ENV{IDF_PATH}/components/esp_tee/subproject/components/tee_sec_storage)
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
project(tee_secure_storage)

View File

@ -0,0 +1,90 @@
| Supported Targets | ESP32-C6 |
| ----------------- | -------- |
# TEE: Secure Storage example
## Overview
- This example demonstrates the ESP-TEE framework's Secure Storage with two workflows:
- Signing and verification
- Create and securely store an ECDSA `secp256r1` keypair in a protected memory space i.e. the secure storage partition
- Sign a message in the TEE (given its digest) using the ECDSA keypair stored in the given slot ID
- Retrieve the ECDSA public key associated with the private key for the given slot ID
- Verify the generated signature in the REE
- Encryption and decryption
- Generate and securely store an AES-256 key in the secure storage partition
- Encrypt a message in the TEE using the AES key stored in the given slot ID with the `aes-256-gcm` algorithm and generate an authentication tag
- Decrypt the ciphertext using the same AES key and validate the authentication tag
- Verify that the decrypted message matches the original
### Notes
- Secure Storage currently supports only the `ecdsa-secp256r1-sha256` algorithm for signing and the `aes-256-gcm` algorithm for encryption.
## How to use the example
### Hardware Required
This example can be executed on any development board with a Espressif SOC chip supporting the TEE framework (see Supported Targets table above).
### Configure the project
Before the project configuration and build, be sure to set the correct chip target using `idf.py set-target <chip_name>`.
Open the project configuration menu (`idf.py menuconfig`).
- Configure the secure storage slot ID for storing the ECDSA keypair at `Example Configuration → TEE: Secure Storage keypair slot ID`.
The TEE Secure Storage feature supports two modes for determining which eFuse block stores the encryption key:
- **Development** Mode: The encryption key is embedded (constant for all instances) in the ESP-TEE firmware.
- **Release** Mode: The encryption key is stored in eFuse BLK4 - BLK9, depending on the `SECURE_TEE_SEC_STG_KEY_EFUSE_BLK` Kconfig option.
#### Configure the eFuse Block ID for Encryption Key Storage
- Navigate to `Security features → Trusted Execution Environment → TEE: Secure Storage Mode` and enable the Release mode configuration.
- Set the eFuse block ID to store the encryption key in `Security features → Trusted Execution Environment → TEE: Secure Storage encryption key eFuse block`.
**Note:** Before running the example, users must program the encryption key into the configured eFuse block - refer to the snippet below. The TEE checks whether the specified eFuse block is empty or already programmed with a key. If the block is empty, an error will be returned; otherwise, the pre-programmed key will be used.
```shell
# Programming the user key (256-bit) in eFuse
# Here, BLOCK_KEYx is a free eFuse key-block between BLOCK_KEY0 and BLOCK_KEY5
espefuse.py -p PORT burn_key BLOCK_KEYx user_key.bin USER
```
### Build and Flash
Build the project and flash it to the board, then run the monitor tool to view the serial output:
```
idf.py -p PORT flash monitor
```
(To exit the serial monitor, type `Ctrl-]`.)
See the Getting Started Guide for full steps to configure and use ESP-IDF to build projects.
### Example Output
```log
I (321) main_task: Calling app_main()
I (321) example_tee_sec_stg: TEE Secure Storage
I (331) example_tee_sec_stg: Message-to-be-signed: Lorem ipsum dolor sit amet, consectetur adipiscing elit.
I (961) Signature: 21 68 98 44 68 04 f2 39 70 f6 dc 79 73 5e 88 80
I (961) Signature: 39 e8 88 4b 41 ea 6f 3c 63 6f 89 08 bf 8d b7 29
I (961) Signature: 97 2b 21 0e 6a 35 a9 86 ee b9 e5 97 e4 b5 3f 76
I (971) Signature: d8 b6 bc d0 7a a6 f9 76 ad 9f e4 6b 3c 50 be 5b
I (1001) example_tee_sec_stg: Signature verified successfully!
I (1001) Plaintext: 4c 6f 72 65 6d 20 69 70 73 75 6d 20 64 6f 6c 6f
I (1001) Plaintext: 72 20 73 69 74 20 61 6d 65 74 2c 20 63 6f 6e 73
I (1011) Plaintext: 65 63 74 65 74 75 72 20 61 64 69 70 69 73 63 69
I (1021) Plaintext: 6e 67 20 65 6c 69 74 2e
I (1111) Encrypted data: 18 85 a2 97 7d 20 be 53 47 b7 3f 6f 52 06 8a 44
I (1111) Encrypted data: 3b 7e 2e 25 7b 33 5d 4f 2a e5 17 5e bc d7 4e 23
I (1111) Encrypted data: 2a 8f 89 a1 80 9c 6c 6b 00 e6 c6 39 7b 3f 75 65
I (1121) Encrypted data: cd d5 f6 f6 3c 9a fb bb
I (1131) Tag: 6d 7f 1f 8e 1e a9 2c d9 d2 7f 9b db 16 cc 9b 68
I (1131) example_tee_sec_stg: Done with encryption/decryption!
I (1141) main_task: Returned from app_main()
```

View File

@ -0,0 +1,2 @@
idf_component_register(SRCS "tee_main.c"
INCLUDE_DIRS "")

View File

@ -0,0 +1,12 @@
menu "Example Configuration"
config EXAMPLE_TEE_SEC_STG_SLOT_ID
int "TEE: Secure Storage keypair slot ID"
default 0
range 0 15
help
This configuration sets the slot ID from the TEE secure storage
storing the ECDSA keypair for executing sign/verify operations
from the TEE side
endmenu

View File

@ -0,0 +1,224 @@
/*
* TEE Secure Storage example
*
* SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Unlicense OR CC0-1.0
*/
#include <stdio.h>
#include <string.h>
#include <inttypes.h>
#include "esp_err.h"
#include "esp_log.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "mbedtls/ecp.h"
#include "mbedtls/ecdsa.h"
#include "mbedtls/sha256.h"
#include "esp_tee.h"
#include "esp_tee_sec_storage.h"
#include "secure_service_num.h"
#define SHA256_DIGEST_SZ (32)
#define ECDSA_SECP256R1_KEY_LEN (32)
#define AES256_GCM_TAG_LEN (16)
#define AES256_GCM_AAD_LEN (16)
#define KEY_SLOT_ID (CONFIG_EXAMPLE_TEE_SEC_STG_SLOT_ID)
#define MAX_AES_PLAINTEXT_LEN (128)
static const char *message = "Lorem ipsum dolor sit amet, consectetur adipiscing elit.";
static const char *TAG = "example_tee_sec_stg";
static esp_err_t verify_ecdsa_secp256r1_sign(const uint8_t *digest, size_t len, const esp_tee_sec_storage_pubkey_t *pubkey, const esp_tee_sec_storage_sign_t *sign)
{
if (pubkey == NULL || digest == NULL || sign == NULL) {
return ESP_ERR_INVALID_ARG;
}
if (len == 0) {
return ESP_ERR_INVALID_SIZE;
}
esp_err_t err = ESP_FAIL;
mbedtls_mpi r, s;
mbedtls_mpi_init(&r);
mbedtls_mpi_init(&s);
mbedtls_ecdsa_context ecdsa_context;
mbedtls_ecdsa_init(&ecdsa_context);
int ret = mbedtls_ecp_group_load(&ecdsa_context.MBEDTLS_PRIVATE(grp), MBEDTLS_ECP_DP_SECP256R1);
if (ret != 0) {
goto exit;
}
size_t plen = mbedtls_mpi_size(&ecdsa_context.MBEDTLS_PRIVATE(grp).P);
ret = mbedtls_mpi_read_binary(&r, sign->sign_r, plen);
if (ret != 0) {
goto exit;
}
ret = mbedtls_mpi_read_binary(&s, sign->sign_s, plen);
if (ret != 0) {
goto exit;
}
ret = mbedtls_mpi_read_binary(&ecdsa_context.MBEDTLS_PRIVATE(Q).MBEDTLS_PRIVATE(X), pubkey->pub_x, plen);
if (ret != 0) {
goto exit;
}
ret = mbedtls_mpi_read_binary(&ecdsa_context.MBEDTLS_PRIVATE(Q).MBEDTLS_PRIVATE(Y), pubkey->pub_y, plen);
if (ret != 0) {
goto exit;
}
ret = mbedtls_mpi_lset(&ecdsa_context.MBEDTLS_PRIVATE(Q).MBEDTLS_PRIVATE(Z), 1);
if (ret != 0) {
goto exit;
}
ret = mbedtls_ecdsa_verify(&ecdsa_context.MBEDTLS_PRIVATE(grp), digest, len, &ecdsa_context.MBEDTLS_PRIVATE(Q), &r, &s);
if (ret != 0) {
goto exit;
}
err = ESP_OK;
exit:
mbedtls_mpi_free(&r);
mbedtls_mpi_free(&s);
mbedtls_ecdsa_free(&ecdsa_context);
return err;
}
static void example_tee_sec_stg_sign_verify(void *pvParameter)
{
char *msg = (char *)pvParameter;
ESP_LOGI(TAG, "Message-to-be-signed: %s", msg);
uint8_t msg_digest[SHA256_DIGEST_SZ];
int ret = mbedtls_sha256((const unsigned char *)msg, strlen(msg), msg_digest, false);
if (ret != 0) {
ESP_LOGE(TAG, "Failed to calculate message hash!");
goto exit;
}
esp_err_t err = esp_tee_sec_storage_clear_slot(KEY_SLOT_ID);
if (err != ESP_OK) {
ESP_LOGE(TAG, "Failed to clear slot %d!", KEY_SLOT_ID);
goto exit;
}
err = esp_tee_sec_storage_gen_key(KEY_SLOT_ID, ESP_SEC_STG_KEY_ECDSA_SECP256R1);
if (err != ESP_OK) {
ESP_LOGE(TAG, "Failed to generate keypair!");
goto exit;
}
esp_tee_sec_storage_sign_t sign = {};
err = esp_tee_sec_storage_get_signature(KEY_SLOT_ID, msg_digest, sizeof(msg_digest), &sign);
if (err != ESP_OK) {
ESP_LOGE(TAG, "Failed to generate signature!");
goto exit;
}
ESP_LOG_BUFFER_HEX("Signature", &sign, sizeof(sign));
esp_tee_sec_storage_pubkey_t pubkey = {};
err = esp_tee_sec_storage_get_pubkey(KEY_SLOT_ID, &pubkey);
if (err != ESP_OK) {
ESP_LOGE(TAG, "Failed to fetch public-key!");
goto exit;
}
err = verify_ecdsa_secp256r1_sign(msg_digest, sizeof(msg_digest), &pubkey, &sign);
if (err != ESP_OK) {
ESP_LOGE(TAG, "Failed to verify signature!");
goto exit;
}
ESP_LOGI(TAG, "Signature verified successfully!");
exit:
vTaskDelete(NULL);
}
static void example_tee_sec_stg_encrypt_decrypt(void *pvParameter)
{
char *plaintext = (char *)pvParameter;
const size_t plaintext_len = strnlen(plaintext, MAX_AES_PLAINTEXT_LEN);
ESP_LOG_BUFFER_HEX("Plaintext", plaintext, plaintext_len);
if (plaintext_len == MAX_AES_PLAINTEXT_LEN && plaintext[MAX_AES_PLAINTEXT_LEN] != '\0') {
ESP_LOGW(TAG, "Plaintext exceeds the max length limit - only the first %d bytes will be encrypted", MAX_AES_PLAINTEXT_LEN);
}
uint8_t tag[AES256_GCM_TAG_LEN];
uint8_t aad_buf[AES256_GCM_AAD_LEN];
memset(aad_buf, 0xA5, sizeof(aad_buf));
esp_err_t err = ESP_FAIL;
uint8_t *ciphertext = calloc(plaintext_len + 1, sizeof(uint8_t));
if (ciphertext == NULL) {
ESP_LOGE(TAG, "Failed to allocate memory");
err = ESP_ERR_NO_MEM;
goto exit;
}
err = esp_tee_sec_storage_clear_slot(KEY_SLOT_ID);
if (err != ESP_OK) {
ESP_LOGE(TAG, "Failed to clear slot %d!", KEY_SLOT_ID);
goto exit;
}
err = esp_tee_sec_storage_gen_key(KEY_SLOT_ID, ESP_SEC_STG_KEY_AES256);
if (err != ESP_OK) {
ESP_LOGE(TAG, "Failed to generate key!");
goto exit;
}
err = esp_tee_sec_storage_encrypt(KEY_SLOT_ID, (uint8_t *)plaintext, plaintext_len, aad_buf, sizeof(aad_buf), tag, sizeof(tag), ciphertext);
if (err != ESP_OK) {
ESP_LOGE(TAG, "Failed to encrypt data!");
goto exit;
}
ESP_LOG_BUFFER_HEX("Encrypted data", ciphertext, plaintext_len);
err = esp_tee_sec_storage_decrypt(KEY_SLOT_ID, (uint8_t *)ciphertext, plaintext_len, aad_buf, sizeof(aad_buf), tag, sizeof(tag), ciphertext);
if (err != ESP_OK || memcmp(ciphertext, plaintext, plaintext_len) != 0) {
ESP_LOGE(TAG, "Encryption verification failed!");
err = ESP_FAIL;
goto exit;
}
ESP_LOG_BUFFER_HEX("Tag", tag, sizeof(tag));
ESP_LOGI(TAG, "Done with encryption/decryption!");
exit:
if (ciphertext != NULL) {
free(ciphertext);
}
vTaskDelete(NULL);
}
void app_main(void)
{
ESP_LOGI(TAG, "TEE Secure Storage");
ESP_ERROR_CHECK(esp_tee_sec_storage_init());
xTaskCreate(example_tee_sec_stg_sign_verify, "tee_sec_stg_sign_verify", 4096, (void *)message, 5, NULL);
xTaskCreate(example_tee_sec_stg_encrypt_decrypt, "tee_sec_stg_encrypt_decrypt", 4096, (void *)message, 5, NULL);
}

View File

@ -0,0 +1,13 @@
# SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
# SPDX-License-Identifier: Unlicense OR CC0-1.0
import pytest
from pytest_embedded import Dut
@pytest.mark.esp32c6
@pytest.mark.generic
def test_example_tee_secure_storage(dut: Dut) -> None:
# Start test
dut.expect('TEE Secure Storage', timeout=30)
dut.expect('Signature verified successfully', timeout=30)
dut.expect('Done with encryption/decryption!', timeout=30)

View File

@ -0,0 +1,3 @@
# Enabling TEE
CONFIG_SECURE_ENABLE_TEE=y
CONFIG_PARTITION_TABLE_SINGLE_APP_TEE=y