From ad74c1c3c20fb027115abfa48e1bbda348cf4932 Mon Sep 17 00:00:00 2001 From: Laukik Hase Date: Tue, 12 Nov 2024 17:03:54 +0530 Subject: [PATCH] feat(esp_tee): Added examples demonstrating the ESP-TEE framework --- examples/security/.build-test-rules.yml | 32 +++ examples/security/README.md | 3 + examples/security/tee/README.md | 37 +++ .../tee/tee_attestation/CMakeLists.txt | 11 + .../security/tee/tee_attestation/README.md | 199 ++++++++++++++ .../tee/tee_attestation/main/CMakeLists.txt | 2 + .../tee/tee_attestation/main/app_main.c | 53 ++++ .../tee_attestation/pytest_tee_attestation.py | 59 ++++ .../tee/tee_attestation/sdkconfig.defaults | 4 + .../security/tee/tee_attestation/version.txt | 1 + .../security/tee/tee_basic/CMakeLists.txt | 9 + examples/security/tee/tee_basic/README.md | 57 ++++ .../example_secure_service/CMakeLists.txt | 11 + .../example_secure_service/example.tbl | 2 + .../example_secure_service/example_service.c | 39 +++ .../include/example_service.h | 20 ++ .../example_secure_service/tee_project.cmake | 15 ++ .../tee/tee_basic/main/CMakeLists.txt | 2 + .../security/tee/tee_basic/main/tee_main.c | 64 +++++ .../tee/tee_basic/pytest_tee_basic.py | 28 ++ .../security/tee/tee_basic/sdkconfig.defaults | 3 + .../tee/tee_secure_ota/CMakeLists.txt | 13 + .../security/tee/tee_secure_ota/README.md | 46 ++++ .../tee/tee_secure_ota/main/CMakeLists.txt | 2 + .../tee/tee_secure_ota/main/Kconfig.projbuild | 17 ++ .../tee/tee_secure_ota/main/app_main.c | 123 +++++++++ .../tee/tee_secure_ota/main/cmd_ota.c | 251 ++++++++++++++++++ .../tee/tee_secure_ota/main/cmd_ota.h | 19 ++ .../tee_secure_ota/pytest_tee_secure_ota.py | 115 ++++++++ .../security/tee/tee_secure_ota/sdkconfig.ci | 11 + .../tee/tee_secure_ota/sdkconfig.defaults | 4 + .../tee_secure_ota/test_certs/server_cert.pem | 20 ++ .../tee_secure_ota/test_certs/server_key.pem | 28 ++ .../tee/tee_secure_storage/CMakeLists.txt | 9 + .../security/tee/tee_secure_storage/README.md | 90 +++++++ .../tee_secure_storage/main/CMakeLists.txt | 2 + .../tee_secure_storage/main/Kconfig.projbuild | 12 + .../tee/tee_secure_storage/main/tee_main.c | 224 ++++++++++++++++ .../pytest_tee_secure_storage.py | 13 + .../tee/tee_secure_storage/sdkconfig.defaults | 3 + 40 files changed, 1653 insertions(+) create mode 100644 examples/security/tee/README.md create mode 100644 examples/security/tee/tee_attestation/CMakeLists.txt create mode 100644 examples/security/tee/tee_attestation/README.md create mode 100644 examples/security/tee/tee_attestation/main/CMakeLists.txt create mode 100644 examples/security/tee/tee_attestation/main/app_main.c create mode 100644 examples/security/tee/tee_attestation/pytest_tee_attestation.py create mode 100644 examples/security/tee/tee_attestation/sdkconfig.defaults create mode 100644 examples/security/tee/tee_attestation/version.txt create mode 100644 examples/security/tee/tee_basic/CMakeLists.txt create mode 100644 examples/security/tee/tee_basic/README.md create mode 100644 examples/security/tee/tee_basic/components/example_secure_service/CMakeLists.txt create mode 100644 examples/security/tee/tee_basic/components/example_secure_service/example.tbl create mode 100644 examples/security/tee/tee_basic/components/example_secure_service/example_service.c create mode 100644 examples/security/tee/tee_basic/components/example_secure_service/include/example_service.h create mode 100644 examples/security/tee/tee_basic/components/example_secure_service/tee_project.cmake create mode 100644 examples/security/tee/tee_basic/main/CMakeLists.txt create mode 100644 examples/security/tee/tee_basic/main/tee_main.c create mode 100644 examples/security/tee/tee_basic/pytest_tee_basic.py create mode 100644 examples/security/tee/tee_basic/sdkconfig.defaults create mode 100644 examples/security/tee/tee_secure_ota/CMakeLists.txt create mode 100644 examples/security/tee/tee_secure_ota/README.md create mode 100644 examples/security/tee/tee_secure_ota/main/CMakeLists.txt create mode 100644 examples/security/tee/tee_secure_ota/main/Kconfig.projbuild create mode 100644 examples/security/tee/tee_secure_ota/main/app_main.c create mode 100644 examples/security/tee/tee_secure_ota/main/cmd_ota.c create mode 100644 examples/security/tee/tee_secure_ota/main/cmd_ota.h create mode 100644 examples/security/tee/tee_secure_ota/pytest_tee_secure_ota.py create mode 100644 examples/security/tee/tee_secure_ota/sdkconfig.ci create mode 100644 examples/security/tee/tee_secure_ota/sdkconfig.defaults create mode 100644 examples/security/tee/tee_secure_ota/test_certs/server_cert.pem create mode 100644 examples/security/tee/tee_secure_ota/test_certs/server_key.pem create mode 100644 examples/security/tee/tee_secure_storage/CMakeLists.txt create mode 100644 examples/security/tee/tee_secure_storage/README.md create mode 100644 examples/security/tee/tee_secure_storage/main/CMakeLists.txt create mode 100644 examples/security/tee/tee_secure_storage/main/Kconfig.projbuild create mode 100644 examples/security/tee/tee_secure_storage/main/tee_main.c create mode 100644 examples/security/tee/tee_secure_storage/pytest_tee_secure_storage.py create mode 100644 examples/security/tee/tee_secure_storage/sdkconfig.defaults diff --git a/examples/security/.build-test-rules.yml b/examples/security/.build-test-rules.yml index ab7537fa4a..7571c73904 100644 --- a/examples/security/.build-test-rules.yml +++ b/examples/security/.build-test-rules.yml @@ -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/**/* diff --git a/examples/security/README.md b/examples/security/README.md index 2d9f53a4c7..38978c5562 100644 --- a/examples/security/README.md +++ b/examples/security/README.md @@ -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. diff --git a/examples/security/tee/README.md b/examples/security/tee/README.md new file mode 100644 index 0000000000..1315154f8b --- /dev/null +++ b/examples/security/tee/README.md @@ -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. diff --git a/examples/security/tee/tee_attestation/CMakeLists.txt b/examples/security/tee/tee_attestation/CMakeLists.txt new file mode 100644 index 0000000000..6d208374ec --- /dev/null +++ b/examples/security/tee/tee_attestation/CMakeLists.txt @@ -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) diff --git a/examples/security/tee/tee_attestation/README.md b/examples/security/tee/tee_attestation/README.md new file mode 100644 index 0000000000..7972351391 --- /dev/null +++ b/examples/security/tee/tee_attestation/README.md @@ -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. + +
+ Attestation: Sample Token + +```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" + } +} +``` +
+ +## 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 `. + +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. + +
+ Attestation: Verifying the generated token + +**Usage**: `python verify_att_token.py ''` + +```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!') +``` +
+ +### 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"}}' +``` \ No newline at end of file diff --git a/examples/security/tee/tee_attestation/main/CMakeLists.txt b/examples/security/tee/tee_attestation/main/CMakeLists.txt new file mode 100644 index 0000000000..a129db3d96 --- /dev/null +++ b/examples/security/tee/tee_attestation/main/CMakeLists.txt @@ -0,0 +1,2 @@ +idf_component_register(SRCS "app_main.c" + INCLUDE_DIRS ".") diff --git a/examples/security/tee/tee_attestation/main/app_main.c b/examples/security/tee/tee_attestation/main/app_main.c new file mode 100644 index 0000000000..90c7c1872b --- /dev/null +++ b/examples/security/tee/tee_attestation/main/app_main.c @@ -0,0 +1,53 @@ +/* + * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Unlicense OR CC0-1.0 + */ +#include +#include +#include + +#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); +} diff --git a/examples/security/tee/tee_attestation/pytest_tee_attestation.py b/examples/security/tee/tee_attestation/pytest_tee_attestation.py new file mode 100644 index 0000000000..9909750bd9 --- /dev/null +++ b/examples/security/tee/tee_attestation/pytest_tee_attestation.py @@ -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!') diff --git a/examples/security/tee/tee_attestation/sdkconfig.defaults b/examples/security/tee/tee_attestation/sdkconfig.defaults new file mode 100644 index 0000000000..8fb9806965 --- /dev/null +++ b/examples/security/tee/tee_attestation/sdkconfig.defaults @@ -0,0 +1,4 @@ +# Enabling TEE +CONFIG_SECURE_ENABLE_TEE=y +CONFIG_ESPTOOLPY_FLASHSIZE_4MB=y +CONFIG_PARTITION_TABLE_TWO_OTA_TEE=y diff --git a/examples/security/tee/tee_attestation/version.txt b/examples/security/tee/tee_attestation/version.txt new file mode 100644 index 0000000000..b82608c0bb --- /dev/null +++ b/examples/security/tee/tee_attestation/version.txt @@ -0,0 +1 @@ +v0.1.0 diff --git a/examples/security/tee/tee_basic/CMakeLists.txt b/examples/security/tee/tee_basic/CMakeLists.txt new file mode 100644 index 0000000000..91f996c02e --- /dev/null +++ b/examples/security/tee/tee_basic/CMakeLists.txt @@ -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) diff --git a/examples/security/tee/tee_basic/README.md b/examples/security/tee/tee_basic/README.md new file mode 100644 index 0000000000..e3bd576494 --- /dev/null +++ b/examples/security/tee/tee_basic/README.md @@ -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 . + +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() +``` diff --git a/examples/security/tee/tee_basic/components/example_secure_service/CMakeLists.txt b/examples/security/tee/tee_basic/components/example_secure_service/CMakeLists.txt new file mode 100644 index 0000000000..0e86e75790 --- /dev/null +++ b/examples/security/tee/tee_basic/components/example_secure_service/CMakeLists.txt @@ -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) diff --git a/examples/security/tee/tee_basic/components/example_secure_service/example.tbl b/examples/security/tee/tee_basic/components/example_secure_service/example.tbl new file mode 100644 index 0000000000..72bd61ff34 --- /dev/null +++ b/examples/security/tee/tee_basic/components/example_secure_service/example.tbl @@ -0,0 +1,2 @@ +# SS no. API type Function Args +201 custom example_sec_serv_aes_op 6 diff --git a/examples/security/tee/tee_basic/components/example_secure_service/example_service.c b/examples/security/tee/tee_basic/components/example_secure_service/example_service.c new file mode 100644 index 0000000000..0be518e82a --- /dev/null +++ b/examples/security/tee/tee_basic/components/example_secure_service/example_service.c @@ -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); +} diff --git a/examples/security/tee/tee_basic/components/example_secure_service/include/example_service.h b/examples/security/tee/tee_basic/components/example_secure_service/include/example_service.h new file mode 100644 index 0000000000..6ea9efa37f --- /dev/null +++ b/examples/security/tee/tee_basic/components/example_secure_service/include/example_service.h @@ -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); diff --git a/examples/security/tee/tee_basic/components/example_secure_service/tee_project.cmake b/examples/security/tee/tee_basic/components/example_secure_service/tee_project.cmake new file mode 100644 index 0000000000..28ffbc57ec --- /dev/null +++ b/examples/security/tee/tee_basic/components/example_secure_service/tee_project.cmake @@ -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) diff --git a/examples/security/tee/tee_basic/main/CMakeLists.txt b/examples/security/tee/tee_basic/main/CMakeLists.txt new file mode 100644 index 0000000000..6746047f5f --- /dev/null +++ b/examples/security/tee/tee_basic/main/CMakeLists.txt @@ -0,0 +1,2 @@ +idf_component_register(SRCS "tee_main.c" + INCLUDE_DIRS "") diff --git a/examples/security/tee/tee_basic/main/tee_main.c b/examples/security/tee/tee_basic/main/tee_main.c new file mode 100644 index 0000000000..6d30288a84 --- /dev/null +++ b/examples/security/tee/tee_basic/main/tee_main.c @@ -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 +#include +#include + +#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!"); + } +} diff --git a/examples/security/tee/tee_basic/pytest_tee_basic.py b/examples/security/tee/tee_basic/pytest_tee_basic.py new file mode 100644 index 0000000000..5887e5406a --- /dev/null +++ b/examples/security/tee/tee_basic/pytest_tee_basic.py @@ -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) diff --git a/examples/security/tee/tee_basic/sdkconfig.defaults b/examples/security/tee/tee_basic/sdkconfig.defaults new file mode 100644 index 0000000000..053f38a496 --- /dev/null +++ b/examples/security/tee/tee_basic/sdkconfig.defaults @@ -0,0 +1,3 @@ +# Enabling TEE +CONFIG_SECURE_ENABLE_TEE=y +CONFIG_PARTITION_TABLE_SINGLE_APP_TEE=y diff --git a/examples/security/tee/tee_secure_ota/CMakeLists.txt b/examples/security/tee/tee_secure_ota/CMakeLists.txt new file mode 100644 index 0000000000..7559f4d7da --- /dev/null +++ b/examples/security/tee/tee_secure_ota/CMakeLists.txt @@ -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) diff --git a/examples/security/tee/tee_secure_ota/README.md b/examples/security/tee/tee_secure_ota/README.md new file mode 100644 index 0000000000..220f4c7d43 --- /dev/null +++ b/examples/security/tee/tee_secure_ota/README.md @@ -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 `. + +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 ` or `user_ota ` + +#### 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. diff --git a/examples/security/tee/tee_secure_ota/main/CMakeLists.txt b/examples/security/tee/tee_secure_ota/main/CMakeLists.txt new file mode 100644 index 0000000000..371fba8ba5 --- /dev/null +++ b/examples/security/tee/tee_secure_ota/main/CMakeLists.txt @@ -0,0 +1,2 @@ +idf_component_register(SRCS "cmd_ota.c" "app_main.c" + INCLUDE_DIRS ".") diff --git a/examples/security/tee/tee_secure_ota/main/Kconfig.projbuild b/examples/security/tee/tee_secure_ota/main/Kconfig.projbuild new file mode 100644 index 0000000000..fe4e60cf72 --- /dev/null +++ b/examples/security/tee/tee_secure_ota/main/Kconfig.projbuild @@ -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 diff --git a/examples/security/tee/tee_secure_ota/main/app_main.c b/examples/security/tee/tee_secure_ota/main/app_main.c new file mode 100644 index 0000000000..6c050b6528 --- /dev/null +++ b/examples/security/tee/tee_secure_ota/main/app_main.c @@ -0,0 +1,123 @@ +/* + * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Unlicense OR CC0-1.0 + */ +#include +#include +#include + +#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 ", + .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 ", + .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(); +} diff --git a/examples/security/tee/tee_secure_ota/main/cmd_ota.c b/examples/security/tee/tee_secure_ota/main/cmd_ota.c new file mode 100644 index 0000000000..29ce2546c2 --- /dev/null +++ b/examples/security/tee/tee_secure_ota/main/cmd_ota.c @@ -0,0 +1,251 @@ +/* + * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Unlicense OR CC0-1.0 + */ +#include +#include +#include + +#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); +} diff --git a/examples/security/tee/tee_secure_ota/main/cmd_ota.h b/examples/security/tee/tee_secure_ota/main/cmd_ota.h new file mode 100644 index 0000000000..0cdc063f3a --- /dev/null +++ b/examples/security/tee/tee_secure_ota/main/cmd_ota.h @@ -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 diff --git a/examples/security/tee/tee_secure_ota/pytest_tee_secure_ota.py b/examples/security/tee/tee_secure_ota/pytest_tee_secure_ota.py new file mode 100644 index 0000000000..0bf3e7b530 --- /dev/null +++ b/examples/security/tee/tee_secure_ota/pytest_tee_secure_ota.py @@ -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() diff --git a/examples/security/tee/tee_secure_ota/sdkconfig.ci b/examples/security/tee/tee_secure_ota/sdkconfig.ci new file mode 100644 index 0000000000..c1d4516e4f --- /dev/null +++ b/examples/security/tee/tee_secure_ota/sdkconfig.ci @@ -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" diff --git a/examples/security/tee/tee_secure_ota/sdkconfig.defaults b/examples/security/tee/tee_secure_ota/sdkconfig.defaults new file mode 100644 index 0000000000..8fb9806965 --- /dev/null +++ b/examples/security/tee/tee_secure_ota/sdkconfig.defaults @@ -0,0 +1,4 @@ +# Enabling TEE +CONFIG_SECURE_ENABLE_TEE=y +CONFIG_ESPTOOLPY_FLASHSIZE_4MB=y +CONFIG_PARTITION_TABLE_TWO_OTA_TEE=y diff --git a/examples/security/tee/tee_secure_ota/test_certs/server_cert.pem b/examples/security/tee/tee_secure_ota/test_certs/server_cert.pem new file mode 100644 index 0000000000..b29ba7ab1f --- /dev/null +++ b/examples/security/tee/tee_secure_ota/test_certs/server_cert.pem @@ -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----- diff --git a/examples/security/tee/tee_secure_ota/test_certs/server_key.pem b/examples/security/tee/tee_secure_ota/test_certs/server_key.pem new file mode 100644 index 0000000000..20a4bdb624 --- /dev/null +++ b/examples/security/tee/tee_secure_ota/test_certs/server_key.pem @@ -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----- diff --git a/examples/security/tee/tee_secure_storage/CMakeLists.txt b/examples/security/tee/tee_secure_storage/CMakeLists.txt new file mode 100644 index 0000000000..9842c6bb84 --- /dev/null +++ b/examples/security/tee/tee_secure_storage/CMakeLists.txt @@ -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) diff --git a/examples/security/tee/tee_secure_storage/README.md b/examples/security/tee/tee_secure_storage/README.md new file mode 100644 index 0000000000..b83898d454 --- /dev/null +++ b/examples/security/tee/tee_secure_storage/README.md @@ -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 `. + +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() +``` diff --git a/examples/security/tee/tee_secure_storage/main/CMakeLists.txt b/examples/security/tee/tee_secure_storage/main/CMakeLists.txt new file mode 100644 index 0000000000..6746047f5f --- /dev/null +++ b/examples/security/tee/tee_secure_storage/main/CMakeLists.txt @@ -0,0 +1,2 @@ +idf_component_register(SRCS "tee_main.c" + INCLUDE_DIRS "") diff --git a/examples/security/tee/tee_secure_storage/main/Kconfig.projbuild b/examples/security/tee/tee_secure_storage/main/Kconfig.projbuild new file mode 100644 index 0000000000..f6ad163e6f --- /dev/null +++ b/examples/security/tee/tee_secure_storage/main/Kconfig.projbuild @@ -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 diff --git a/examples/security/tee/tee_secure_storage/main/tee_main.c b/examples/security/tee/tee_secure_storage/main/tee_main.c new file mode 100644 index 0000000000..9bb73e22d3 --- /dev/null +++ b/examples/security/tee/tee_secure_storage/main/tee_main.c @@ -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 +#include +#include + +#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); +} diff --git a/examples/security/tee/tee_secure_storage/pytest_tee_secure_storage.py b/examples/security/tee/tee_secure_storage/pytest_tee_secure_storage.py new file mode 100644 index 0000000000..42ed68b7d1 --- /dev/null +++ b/examples/security/tee/tee_secure_storage/pytest_tee_secure_storage.py @@ -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) diff --git a/examples/security/tee/tee_secure_storage/sdkconfig.defaults b/examples/security/tee/tee_secure_storage/sdkconfig.defaults new file mode 100644 index 0000000000..053f38a496 --- /dev/null +++ b/examples/security/tee/tee_secure_storage/sdkconfig.defaults @@ -0,0 +1,3 @@ +# Enabling TEE +CONFIG_SECURE_ENABLE_TEE=y +CONFIG_PARTITION_TABLE_SINGLE_APP_TEE=y